From 702cdfef8f59f1198234ac6e43094523968f8e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:20:01 +0100 Subject: [PATCH 01/67] refactor User Model (#8) --- .../use_cases/security/GetAccessToken.js | 1 + lib/application/use_cases/user/CreateUser.js | 5 ++-- lib/domain/model/User.js | 8 +++--- lib/domain/model/utils/EtatsEnum.js | 4 --- .../orm/sequelize/models/Etat.js | 19 -------------- .../orm/sequelize/models/Utilisateur.js | 6 +++++ lib/infrastructure/orm/sequelize/sequelize.js | 24 +---------------- .../repositories/UserRepository.js | 6 +++-- package-lock.json | 19 -------------- test/integration/users.test.js | 26 +++++++++++++++++-- .../usecase/fixtures/searchFixture.js | 4 ++- .../application/usecase/user/user.test.js | 17 +++++++++--- 12 files changed, 60 insertions(+), 79 deletions(-) delete mode 100644 lib/domain/model/utils/EtatsEnum.js delete mode 100644 lib/infrastructure/orm/sequelize/models/Etat.js diff --git a/lib/application/use_cases/security/GetAccessToken.js b/lib/application/use_cases/security/GetAccessToken.js index 09fdc08..cef3d32 100644 --- a/lib/application/use_cases/security/GetAccessToken.js +++ b/lib/application/use_cases/security/GetAccessToken.js @@ -6,6 +6,7 @@ module.exports = async (email, password, { userRepository, accessTokenManager }) if (!user || !await bcrypt.compare(password,user.password)) { throwStatusCode(401,'Bad credentials') } + return accessTokenManager.generate({ sub: 'my-sub', // needs to match definition above value: user.id, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index afccae1..d10dba7 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -2,7 +2,6 @@ const User = require('../../../domain/model/User'); const bcrypt = require("bcrypt"); -const etatsEnum = require('../../../domain/model/utils/EtatsEnum') const rolesEnum = require('../../../domain/model/utils/RolesEnum') const throwStatusCode = require("../utils/throwStatusCode") module.exports = async (pseudo, email,alias, bio, password,spotifyToken, { userRepository }) => { @@ -20,10 +19,12 @@ module.exports = async (pseudo, email,alias, bio, password,spotifyToken, { userR email, alias, bio, + null, + null, password, spotifyToken, rolesEnum.UTILISATEUR, - etatsEnum.LIBRE); + null); return userRepository.persist(user) diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index d5c2153..1293a12 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -2,16 +2,18 @@ module.exports = class { - constructor(id = null, pseudo, email,alias, bio, password,spotifyToken,id_role, id_etat) { + constructor(id = null, pseudo, email,alias, bio, photo,tempPhoto, password,spotifyToken,id_role, banUntil) { this.id = id; this.pseudo = pseudo; this.email = email; this.alias = alias - this.bio = bio + this.photo = photo + this.tempPhoto = tempPhoto this.spotifyToken = spotifyToken this.password = password; + this.password = password; this.id_role = id_role; - this.id_etat = id_etat + this.banUntil = banUntil this.type = 'user' } diff --git a/lib/domain/model/utils/EtatsEnum.js b/lib/domain/model/utils/EtatsEnum.js deleted file mode 100644 index 20f5d9c..0000000 --- a/lib/domain/model/utils/EtatsEnum.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - LIBRE : 1, - BAN : 2, -} \ No newline at end of file diff --git a/lib/infrastructure/orm/sequelize/models/Etat.js b/lib/infrastructure/orm/sequelize/models/Etat.js deleted file mode 100644 index 0241add..0000000 --- a/lib/infrastructure/orm/sequelize/models/Etat.js +++ /dev/null @@ -1,19 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (sequelize) => { - - return sequelize.define('etat', { - - // attributes - id_etat: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - libelle: { - type: DataTypes.STRING(20), - allowNull: false, - }, - }, - { freezeTableName: true,}); - -}; diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index 84fac16..22ff063 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -38,6 +38,12 @@ module.exports = (sequelize) => { photo: { type: DataTypes.STRING(250), }, + photo_temporaire: { + type: DataTypes.STRING(250), + }, + ban_until: { + type: DataTypes.DATE + }, bio: { type: DataTypes.STRING(1500), }, diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index 35a6951..f828b62 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -9,8 +9,6 @@ const ArtisteModel = require('./models/Artiste')(sequelize) const ReviewModel = require('./models/Review')(sequelize) const TypeReviewModel = require('./models/TypeReview')(sequelize) const CommentaireModel = require('./models/Commentaire')(sequelize) -const EtatModel = require('./models/Etat')(sequelize) - require('./models/LikeOeuvre')(sequelize) require('./models/OeuvreFavorite')(sequelize) @@ -21,17 +19,8 @@ UserModel.belongsTo(RoleModel, {foreignKey: 'id_role'}) //un utilisateur a un etat -UserModel.belongsTo(EtatModel, - {foreignKey: 'id_etat'}) -//un utilisateur peut avoir plusieurs amis -UserModel.belongsToMany(UserModel, - { - as: 'amis_1', - foreignKey: 'amis_1_id', - through : AmisModel - } -) + UserModel.belongsToMany(UserModel, { as: 'amis_2', @@ -117,15 +106,4 @@ RoleModel.sync() console.error('Erreur dans la création des roles : '+error) }) -EtatModel.sync() - .then(()=>{ - return EtatModel.bulkCreate([ - {id_etat: 1, libelle : 'libre'}, - {id_etat: 2, libelle : 'ban'}, - ]) - }) - .then(() => console.log('Creation des etats réussi')) - .catch((error) => { - console.error('Erreur dans la création des etats : '+error) - }) module.exports = sequelize; \ No newline at end of file diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index e8e94ab..2ad0dd2 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -38,7 +38,7 @@ module.exports = class extends userRepository { { pseudo : ident}, { email : ident} ] - } + }, }); return this.createUser(seqUser) } @@ -53,10 +53,12 @@ module.exports = class extends userRepository { seqUser.email, seqUser.alias, seqUser.bio, + seqUser.photo, + seqUser.photo_temporaire, seqUser.password, seqUser.token_spotify, seqUser.id_role, - seqUser.id_etat); + seqUser.ban_until); } async getByEmailOrPseudo(email, pseudo){ const seqUser = await this.model.findOne({ diff --git a/package-lock.json b/package-lock.json index bd5d4df..8b62635 100644 --- a/package-lock.json +++ b/package-lock.json @@ -414,25 +414,6 @@ } } }, - "@hapi/basic": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/basic/-/basic-5.1.1.tgz", - "integrity": "sha512-fHyVvf2xurgGBJJaLpRMtDF4AaPSs679nGk8/FjBw3p8/Kj5fuPv5kD1+fJ4ZSdPt1rHHGvKIDK0aVysbbUDMg==", - "requires": { - "@hapi/boom": "7.x.x", - "@hapi/hoek": "8.x.x" - }, - "dependencies": { - "@hapi/boom": { - "version": "7.4.11", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-7.4.11.tgz", - "integrity": "sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==", - "requires": { - "@hapi/hoek": "8.x.x" - } - } - } - }, "@hapi/boom": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.0.tgz", diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 9d25b15..7af5362 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -220,7 +220,18 @@ describe('user route', () => { it('should respond code 200', async () => { const password = 'password' - let fetchedUser = new User("id","pseudo","email","alias","bio",await bcrypt.hash(password,10),"token",1,1) + const fetchedUser = new User( + "id", + "pseudo", + "email", + "alias", + "bio", + "path/to/file", + "path/to/file", + await bcrypt.hash(password,10), + "token", + 1, + new Date("10-06-2003")) mockUserRepository.getByIdent = jest.fn((ident) =>{ return fetchedUser @@ -240,7 +251,18 @@ describe('user route', () => { }) it('should respond code 401 bad password', async () => { const password = 'password' - let fetchedUser = new User("id","pseudo","email","alias","bio",await bcrypt.hash(password,10),"token",1,1) + const fetchedUser = new User( + "id", + "pseudo", + "email", + "alias", + "bio", + "path/to/file", + "path/to/file", + await bcrypt.hash(password,10), + "token", + 1, + new Date("10-06-2003")) mockUserRepository.getByIdent = jest.fn((ident) =>{ return fetchedUser diff --git a/test/unit/application/usecase/fixtures/searchFixture.js b/test/unit/application/usecase/fixtures/searchFixture.js index 049588e..8b81274 100644 --- a/test/unit/application/usecase/fixtures/searchFixture.js +++ b/test/unit/application/usecase/fixtures/searchFixture.js @@ -25,10 +25,12 @@ const mockUser = new User( 'testEmail@gmail.com', 'test_alias', 'testbio', + 'path/to/photo', + 'path/to/photo', 'passwordtest', 'spotifyToken', 2, - 1 + new Date("10-06-2003") ) const SpotifyRepositoryFixture = { tracks : { items : [rawTrackWithOneArtistOneAlbum]}, diff --git a/test/unit/application/usecase/user/user.test.js b/test/unit/application/usecase/user/user.test.js index 39572ad..938560d 100644 --- a/test/unit/application/usecase/user/user.test.js +++ b/test/unit/application/usecase/user/user.test.js @@ -11,10 +11,13 @@ const persistedUser = new User( 'testEmail@gmail.com', 'test_alias', 'testbio', + 'path/to/file', + 'path/to/file', 'passwordtest', 'spotifyToken', 2, - 1) + new Date("10-06-2003") +) describe('createUser', () =>{ @@ -40,7 +43,9 @@ describe('createUser', () =>{ expect(mockResult.bio).toBe(persistedUser.bio) expect(mockResult.spotifyToken).toBe(persistedUser.spotifyToken) expect(mockResult.id_role).toBe(2) - expect(mockResult.id_etat).toBe(1) + expect(mockResult.photo).toBe(null) + expect(mockResult.tempPhoto).toBe(null) + expect(mockResult.banUntil).toBe(null) expect(mockResult.password).not.toBe(user.password) }) it('should throw an error when an user with the same pseudo exists', async ()=>{ @@ -83,10 +88,12 @@ describe('getAccessToken', () =>{ 'testEmail@gmail.com', 'test_alias', 'testbio', + 'path/to/file', + 'path/to/file', await bcrypt.hash(passwordTest,10), 'spotifyToken', 2, - 1) + new Date("10-06-2003")) const mockUserRepository = new UserRepository(); const mockAccessTokenManager = {}; mockAccessTokenManager.generate = jest.fn((uid) => 1) @@ -112,10 +119,12 @@ describe('getAccessToken', () =>{ 'testEmail@gmail.com', 'test_alias', 'testbio', + 'path/to/file', + 'path/to/file', await bcrypt.hash(passwordTest,10), 'spotifyToken', 2, - 1) + new Date("10-06-2003")) const mockUserRepository = new UserRepository(); const mockAccessTokenManager = {}; mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) From aebd3cb56141a950ed47d9fed0303fb455e83c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:36:04 +0100 Subject: [PATCH 02/67] =?UTF-8?q?refactor=20de=20la=20mise=20en=20place=20?= =?UTF-8?q?de=20la=20strat=C3=A9gie=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/infrastructure/config/strategy.js | 21 +++++++++++++++++++++ lib/infrastructure/webserver/server.js | 21 ++------------------- test/integration/users.test.js | 7 +++++++ 3 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 lib/infrastructure/config/strategy.js diff --git a/lib/infrastructure/config/strategy.js b/lib/infrastructure/config/strategy.js new file mode 100644 index 0000000..3250398 --- /dev/null +++ b/lib/infrastructure/config/strategy.js @@ -0,0 +1,21 @@ +module.exports = ({userRepository}) =>{ + return { + keys: process.env.SECRET_ENCODER, // replace with your secret key for signing and verifying JWTs + verify: { + aud: 'urn:audience:test', + iss: 'urn:issuer:test', + sub: false, + nbf: true, + exp: true, + maxAgeSec: 14400, // 4 hours + timeSkewSec: 15 + }, + validate: async (artifacts, request, h) => { + const {userRepository} = server.app.serviceLocator + const isValid = !!await userRepository.getByUser(artifacts.decoded.payload.value) + return { + isValid, + }; + }, + } +} \ No newline at end of file diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 9317bf7..6c779c7 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -10,6 +10,7 @@ const Package = require('../../../package'); const routes = require('../../interfaces/routes'); const serviceLocator = require('../../infrastructure/config/service-locator') const Jwt = require('@hapi/jwt'); +const strategy = require("../config/strategy") const createServer = async () => { // Create a server with a host and port @@ -64,25 +65,7 @@ const createServer = async () => { - server.auth.strategy('jwt', 'jwt', { - keys: process.env.SECRET_ENCODER, // replace with your secret key for signing and verifying JWTs - verify: { - aud: 'urn:audience:test', - iss: 'urn:issuer:test', - sub: false, - nbf: true, - exp: true, - maxAgeSec: 14400, // 4 hours - timeSkewSec: 15 - }, - validate: async (artifacts, request, h) => { - const {userRepository} = server.app.serviceLocator - const isValid = !!await userRepository.getByUser(artifacts.decoded.payload.value) - return { - isValid, - }; - }, - }); + server.auth.strategy('jwt', 'jwt', strategy(serviceLocator)); await server.register([ require('../../interfaces/routes/users'), diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 7af5362..9a02199 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -2,9 +2,12 @@ const Hapi = require('@hapi/hapi'); const User = require("../../lib/domain/model/User") const bcrypt = require("bcrypt"); +const strategy = require("../../lib/infrastructure/config/strategy"); +const Jwt = require("@hapi/jwt"); let server const mockUserRepository = {} const mockAccesTokenManager = {} +require('dotenv').config() mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => { return null }) @@ -22,10 +25,14 @@ describe('user route', () => { await server.register([ require('../../lib/interfaces/routes/users'), ]); + server.register(Jwt) + server.app.serviceLocator = { userRepository: mockUserRepository, accessTokenManager:mockAccesTokenManager } + server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + }); afterEach(async () => { From 806bdacadef2bb9a847b661a24d3d28eed179605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:18:44 +0100 Subject: [PATCH 03/67] Feature/profile upload (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * first try upload image * save * Création de la route /users/uploadPreviewProfile * save * test & correction --- .gitignore | 1 + .../use_cases/image/uploadImage.js | 16 ++++ .../use_cases/user/uploadPreview.js | 16 ++++ lib/application/use_cases/utils/isImage.js | 5 ++ lib/infrastructure/config/service-locator.js | 2 + lib/infrastructure/config/strategy.js | 1 - .../repositories/DocumentRepository.js | 33 ++++++++ .../repositories/UserRepository.js | 23 ++++- .../interfaces/DoucumentRepositoryAbstract.js | 16 ++++ .../interfaces/UserRepositoryAbstract.js | 7 +- lib/interfaces/controllers/ImageController.js | 22 +++++ lib/interfaces/controllers/UsersController.js | 20 +++++ .../controllers/utils/handleError.js | 2 +- lib/interfaces/routes/image.js | 47 ++++++++++ lib/interfaces/routes/users.js | 38 +++++++++ test/integration/fixture/imageTest.jpg | Bin 0 -> 127408 bytes test/integration/users.test.js | 52 ++++++++++-- .../usecase/user/uploadPreview.test.js | 80 ++++++++++++++++++ .../repositories/DocumentRepository.test.js | 28 ++++++ .../1593d51b7bec762dc17fbc8833c18c7c.png | 1 + 20 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 lib/application/use_cases/image/uploadImage.js create mode 100644 lib/application/use_cases/user/uploadPreview.js create mode 100644 lib/application/use_cases/utils/isImage.js create mode 100644 lib/infrastructure/repositories/DocumentRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/DoucumentRepositoryAbstract.js create mode 100644 lib/interfaces/controllers/ImageController.js create mode 100644 lib/interfaces/routes/image.js create mode 100644 test/integration/fixture/imageTest.jpg create mode 100644 test/unit/application/usecase/user/uploadPreview.test.js create mode 100644 test/unit/infrastructure/repositories/DocumentRepository.test.js create mode 100644 test/unit/infrastructure/repositories/testFolder/1593d51b7bec762dc17fbc8833c18c7c.png diff --git a/.gitignore b/.gitignore index 326bcde..cad71b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ node_modules .idea/ +upload .env diff --git a/lib/application/use_cases/image/uploadImage.js b/lib/application/use_cases/image/uploadImage.js new file mode 100644 index 0000000..93d9d92 --- /dev/null +++ b/lib/application/use_cases/image/uploadImage.js @@ -0,0 +1,16 @@ +'use strict'; +const crypto = require('crypto'); +const {writeFile} = require("fs"); +module.exports = async (image) => { + return new Promise((resolve, reject) => { + const fileLabel = crypto.randomBytes(16).toString('hex') + const fileExt = image.hapi.filename.split(".")[1] + const path = `upload/${fileLabel}.${fileExt}` + writeFile(`./${path}`, image._data, err => { + if (err) { + reject(err) + } + resolve({ path }) + }) + }) +}; diff --git a/lib/application/use_cases/user/uploadPreview.js b/lib/application/use_cases/user/uploadPreview.js new file mode 100644 index 0000000..fd4f604 --- /dev/null +++ b/lib/application/use_cases/user/uploadPreview.js @@ -0,0 +1,16 @@ +'use strict'; +const isImage = require("../utils/isImage") +const throwStatusCode = require("../utils/throwStatusCode") +module.exports = async (file,token,{accessTokenManager, userRepository,documentRepository}) => { + if(!isImage(file)) throwStatusCode('415',"le fichier fourni n'est pas une image") + const id = accessTokenManager.decode(token)?.value + if(! await userRepository.getByUser(id)) throwStatusCode('401',"votre token d'authentification n'est pas le bon") + const previewPath = await userRepository.getPreviewPath(id) + if(previewPath) { + documentRepository.deleteFile(previewPath) + } + const path = await documentRepository.uploadFile('upload',file) + if(! path) throwStatusCode(500, "internal server error") + userRepository.addPreviewPath(id,path) + return path +}; \ No newline at end of file diff --git a/lib/application/use_cases/utils/isImage.js b/lib/application/use_cases/utils/isImage.js new file mode 100644 index 0000000..3df302c --- /dev/null +++ b/lib/application/use_cases/utils/isImage.js @@ -0,0 +1,5 @@ +const acceptedHeaders = ['image/png','image/jpeg'] +module.exports = (file) => { + const header = file?.hapi?.headers['content-type'] + return acceptedHeaders.includes(header) && file?.hapi?.filename && file._data +} \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index de5db8a..01c17b7 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -5,12 +5,14 @@ const environment = require('./environment'); const UserRepository= require('../repositories/UserRepository'); const spotifyRepository= require('../repositories/SpotifyRepository'); const JwtAccessTokenManager = require('../security/JwtAccessTokenManager'); +const documentRepository= require('../repositories/DocumentRepository'); function buildBeans() { return { accessTokenManager: new JwtAccessTokenManager(), userRepository: new UserRepository(), spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET), + documentRepository: new documentRepository() }; } diff --git a/lib/infrastructure/config/strategy.js b/lib/infrastructure/config/strategy.js index 3250398..63867b4 100644 --- a/lib/infrastructure/config/strategy.js +++ b/lib/infrastructure/config/strategy.js @@ -11,7 +11,6 @@ module.exports = ({userRepository}) =>{ timeSkewSec: 15 }, validate: async (artifacts, request, h) => { - const {userRepository} = server.app.serviceLocator const isValid = !!await userRepository.getByUser(artifacts.decoded.payload.value) return { isValid, diff --git a/lib/infrastructure/repositories/DocumentRepository.js b/lib/infrastructure/repositories/DocumentRepository.js new file mode 100644 index 0000000..ae1b953 --- /dev/null +++ b/lib/infrastructure/repositories/DocumentRepository.js @@ -0,0 +1,33 @@ +'use strict'; +const documentRepositoryAbstract = require('./interfaces/DoucumentRepositoryAbstract') +const {unlink, writeFile} = require("fs"); +const crypto = require("crypto"); + +module.exports = class extends documentRepositoryAbstract{ + + uploadFile(folderPath,file) { + return new Promise((resolve, reject) => { + const fileLabel = crypto.randomBytes(16).toString('hex') + const fileExt = file.hapi.filename.split(".")[1] + const path = `${folderPath}/${fileLabel}.${fileExt}` + writeFile(`./${path}`, file._data, err => { + if (err) { + reject(err) + } + resolve(path) + }) + }) + } + deleteFile(path){ + unlink(`./${path}`,err => { + if(err){ + throw err + } + }) + } + + + + + +}; diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index 2ad0dd2..23f1d02 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -3,7 +3,7 @@ const sequelize = require('../orm/sequelize/sequelize'); const user = require('../../domain/model/User'); const userRepository = require('./interfaces/UserRepositoryAbstract'); -const { Op } = require('sequelize'); +const { Op,col,literal } = require('sequelize'); const {id} = require("../../domain/model/User"); const {create} = require("underscore"); module.exports = class extends userRepository { @@ -87,4 +87,25 @@ module.exports = class extends userRepository { }); return this.createUser(seqUser) } + async getPreviewPath(id){ + const url = await this.model.findOne({ + where: { + [Op.and]: [ + { id_utilisateur: id }, + sequelize.literal('NOT `photo_temporaire` <=> `photo`'), + ] + }, + attributes: ['photo_temporaire'], + raw:true + }); + return url?.photo_temporaire + } + async addPreviewPath(id,path){ + const [updatedRowsCount, updatedRows] = await this.model.update( + {photo_temporaire: path}, + {where: {id_utilisateur: id}} + ) + return updatedRowsCount + } }; + diff --git a/lib/infrastructure/repositories/interfaces/DoucumentRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/DoucumentRepositoryAbstract.js new file mode 100644 index 0000000..3ff027f --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/DoucumentRepositoryAbstract.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports = class { + + uploadFile(path,file) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + deleteFile(path){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + + + + +}; diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index 22f2528..44318ec 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -19,5 +19,10 @@ module.exports = class { async getByUser(id){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - + async getPreviewPath(id){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + async addPreviewPath(id,path){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/interfaces/controllers/ImageController.js b/lib/interfaces/controllers/ImageController.js new file mode 100644 index 0000000..c74d821 --- /dev/null +++ b/lib/interfaces/controllers/ImageController.js @@ -0,0 +1,22 @@ +'use strict'; + +const uploadImage = require('../../application/use_cases/image/uploadImage'); + +module.exports = { + + async upload(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const {image} = request.payload + const {path} = await uploadImage(image) + try{ + return handler + .response({ + url : `${request.server.info.uri}/${path}`, + }) + } + catch (error){ + console.log(error) + return handler.response(error).code(500) + } + } +}; diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index e572d16..c080570 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -3,6 +3,7 @@ const CreateUser = require('../../application/use_cases/user/CreateUser'); const GetAccessToken = require('../../application/use_cases/security/GetAccessToken'); const handleError = require("./utils/handleError") +const uploadPreview = require("../../application/use_cases/user/uploadPreview") module.exports = { async createUser(request,handler) { @@ -33,5 +34,24 @@ module.exports = { catch (error){ return handleError(error) } + }, + async uploadPreviewProfile(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {file} = request.payload + try{ + return handler.response({ + path: `${request.server.info.uri}/${await uploadPreview(file,token, serviceLocator)}` + }) + } + catch (e){ + return handleError(e) + } + // Now 'token' contains the bearer token + + // Perform any necessary authentication logic here + + return handler.response('Access granted!'); } }; diff --git a/lib/interfaces/controllers/utils/handleError.js b/lib/interfaces/controllers/utils/handleError.js index 2b19f3d..caa37d7 100644 --- a/lib/interfaces/controllers/utils/handleError.js +++ b/lib/interfaces/controllers/utils/handleError.js @@ -1,7 +1,7 @@ const Boom = require("@hapi/boom") module.exports = (error) => { - const statusCode = error.code ? error.code : 500 + const statusCode = error?.code ? error.code : 500 console.log(error) return Boom.boomify(error, {statusCode: statusCode}) } \ No newline at end of file diff --git a/lib/interfaces/routes/image.js b/lib/interfaces/routes/image.js new file mode 100644 index 0000000..249bbe7 --- /dev/null +++ b/lib/interfaces/routes/image.js @@ -0,0 +1,47 @@ +'use strict'; +const Joi = require('@hapi/joi') +const ImageController = require("../controllers/ImageController") +const MAX_BYTE_SIZE =20971520 +module.exports = { + name: 'image', + version: '1.0.0', + register: async (server) => { + server.route([ + { + method: 'POST', + path: '/image/upload', + handler: ImageController.upload, + options: { + payload: { + maxBytes: MAX_BYTE_SIZE, // Set your desired maximum payload size in bytes + output: 'stream', + parse: true, + allow: 'multipart/form-data', + multipart: true, // Set multipart to true for handling file uploads + }, + description: 'upload an image', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + image: Joi.any().required(), + }) + } + }, + }, + ]); + } +}; \ No newline at end of file diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index cd0415e..a01ccfb 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -1,6 +1,8 @@ 'use strict'; const {userSignUp, userSignIn} = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); +const Joi = require('joi') +const MAX_BYTE_SIZE =20971520 module.exports = { name: 'users', version: '1.0.0', @@ -64,6 +66,42 @@ module.exports = { } }, }, + { + method: 'POST', + path: '/users/uploadPreviewProfile', + handler: UsersController.uploadPreviewProfile, + options: { + payload: { + maxBytes: MAX_BYTE_SIZE, // Set your desired maximum payload size in bytes + output: 'stream', + parse: true, + allow: 'multipart/form-data', + multipart: true, // Set multipart to true for handling file uploads + }, + auth: 'jwt', + description: 'allow to upload preview profile picture', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + file: Joi.any() + }) + } + }, + } ]); } }; \ No newline at end of file diff --git a/test/integration/fixture/imageTest.jpg b/test/integration/fixture/imageTest.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a723e0363e7a4459ea747e5c95754bcbdd247ec9 GIT binary patch literal 127408 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

^US<&)--RD zX!vR6HwIN5CBawmr4)m}w9;g)FZ9kfsnh--883|2zUuKHJ z4LIp`_j--{%zPO<|3y6GKTL)at=LIzc&=G3!}RJ&3DU3DUFqhb`e~86d*UsgdPORW zyCT80snj?+uK|iFxR`!n;Id;zJ0}%+3^uae~Ds`M2MrFO+Z_w5^wbH1J`>y&K700_CBhgYXHl!0c$Vx!y#$o7#C1 z3E_#JIA#(VSMI>txM*ecX62v&ldUtl2KAL{S5P;%;X=D!am<`-#k2W9Jfi{u-v&EM z%~1Z?1ZbANH_J5+VLWpRQ8neuik#{m*#zQLhhF6PkP)X$a@7xsn(m)4aydh{QjuSq z%(W21KjLmilzQ9-t-(E}au%(mJ!VC@E>bAG_gUCzt z>1lRV=(Z0!VUCE=XqQGNUV%V$RdWmd8rMGG3hTb$9C6lePdt4RiFoKfhOhb^3U`plM#IVBxD0x$c*bl*kWiiso z5upfC)I<<*W65PEa}hgRO2)tV2k?dfln^(nrz`zME8XgpuR7X$Xm#pFc)|P#8)T`M zZq-CI3bry4rXzq9gOm`%RR;uH>;B{RqO22-HxPD7pSbZ&Qr*Wi%q-d4u5C?H25FvW&z$EHfhHX$yvw3LcyPHm)Vd{=xoQ9V*W>$ia~M`LX)5#beCn})Z^s-_|_ z51!TAlTsQD2uL5o(lVBl6!px|VkD>vKRZysl4Iup!FHILG|pg1U5e|}&m+YKX@*1k z!|p0FKUg~3r06&dn=6D6figDR*n@5pj&jOO$stCAB+W?y^EwZFPZ#yVvQ#n^0=b*? zTtPKIH3TXMJ600J&ElTL%`1};Cipa;ndo`R;zD&ma@sKkuUqOs!HOi1T6JmlfHSS5 z1D#4AR+^(BAOQ-i2N+|9a@_imTV2Sp3qHz^@ppLJ^7tV=Yf2%|`;VMWX>HmEN)#~) zZ-|!ZEwE5alKF0_G}`1{w2AqM_a;fK$WsgAO}k6m_kxvmaq(qt{jWHs0ZK@!%tA++ zJ|hrVnLF&jEsHn~Po~$Twu9llQmv0|d4Rz#Q_PtVakAhkuK`=z@pR0gXaW(0o1f7( zFT%bS{oQaO+wcZKcA8EbDT&(8M3!>!s1wpE?m{bvC_y`acPHqRO&xrf4Sid&Q6se{ zC@lkc3gj*v>O6t?a>SSbUQ9Nd^yGNH!7HO!*g~7ipbOP0F|}N$)n1=b*l*D0O4gEo zn?%(Js-jzDGh(QP`3DJ6DF+A!b2}kk!^jyh9K;|bSog?hh zyEGx(m#)f8dN;u|i`5Ia=CG8^blNHN3I7F(0mJ^;ZnIzxzW*loU8_0)=M^GtYtkw#gDBoOQ%9j%xmH;AStHY_Vt>*MmHu^Y>+qGes?i?6}Dm! zwiv{XfZ?uLwR_8e7x!*sO25+82&-9?SmEmg^WAjHZl6I#scLzTZ|q-B`Mne+6F7Bm zRexJhM3xY8T}8Q1nb$bQ+bE$#HW^_Eu#pxmtHvv3EUHGR_KRmip9oxM(h_u=cXeBL z8qiXI3qG*WRw{p6_9Ra^1cl71)$rI7clIaTB(lgo)8_QJqA?u-}`ov}MR$SQ*FcPp*fx8-#eH$dn-%;Z$x`hTwMD;f7bZX(9uf_=}1t ztHDNp79DW9YUS+tGXw_Ot==8>d#~Kw2Wj-r&e7u5t(?%JW|+c!2(RBFy<~DG_WcXJ z+(n2!Xcc#>zvT21Co_79f?yhEpOx@k0*)ORB@eVreUk5VG>C zZyG@{bfv|!{2gCKj8FYN$NLD3ELY+-{?=)jj$~StD?{8X-V66_noEsVb!$ZO7Rq*y zyh=vSx0OSVUM$TqEXZI7OpVkY`%*nQ3Foc_^GJW~^ri~cIxvghr!I5#f6UN<@7D-j z!C0mG2}VSqw^2-0H34JtU{v-+Lkri!)#S5H^%+=$tYZA46s-j+Y^CaXuUR`5U5#;2 za>uq}(SDevAmYTT8I$^A$(o_2lQ5!8P|A(I%4*dkQ9b)=7)Y~6`vF&3s?Q*lg%hex z*TJg$^39TG*qVR_FHj%VI#Z~-QuWLvNg!4+OJG8;uF4slZvH~?&_8j|)5KO5K9D6y z@Y~#Ws)}_$M__u}=@T)Pj>A+)dTj81h3c91dPPptjP^>15v3<6jluy>zN(tIXwpQz zO!#erY-e=Kla__kU3JRYzPmSzKl(YP)cf1wluR)DAZIZ}?80~!yCd#E;_r)a<@)%NWn zNeB$$H^R(XracvHHe5YLd)QK@XzoK?1p(u!HHv7N1#U_%Q>Ga37rrl_S9z;rkO7b%DBU23s&;0O5%}t{xAY33!oNePYw(%${hDhs}&j zhD6&x209Y(1I-n^L^G?1H)#{d!h1!dpaaPQ_ZjIV?1t&M28t^>G!Zs1haSxWGxl3+ zv>+efsKGw(m_s?g$PXS`W;HS6nrKv>g|IXTd<|qx+ziw7w8Q{+s+1Vs6B~1eJmwU6 z4Ec#+KL5viQgY&8cuz4kumg}w!VgzfJ%-!#O&K1IV{DV8iGSbpxxLfo6OFgc7ymD} z7heT?Ztn0{NA6z3>#Q_cP!RHzJu9qKmcWrdA?f)3_2_F!$p)=WhwzLV)LBuyvUP57 zh5w6@Rjfd!-b9Mp%Pobn%sqs!?__63>6r1y92-TksAEMV)5;aim?_?t&I-2TXIqZg z`tg9cm>fY0j#l$G1*{{9zu3zWiX;`nV|EXCg^YYN4gNCxRP81y1m7e+IwwEQrhJy- z7;ctd3yv4NEkOL5@|wnZ;FXAcT4|RxduSBAUF*r5b$IeIb%*K!epb?pw!LM>Sad7AyIhQn5UxA4x$;7W;|7Jy zOAEhuE5gIBE}5g^E_$*s&o`<1Y4TIzG#^ zS>(&NCe0HMh?m^Lbk9p%D87dc%Dy4tzh+&u2-Hz{>8ax1922xD_PyfTLM8dWAP9e+Z|WawPdz&B-2n*D^YaD&Fgv&t^=F9hCc#IrLli@~t3(g_Qgxs;=h$$5XvWp2ThIV6=k7cyuh(L& zUxte9S91TZRm6)F&j(T3#SCMAB&B{rKRNF~l6T(NkCb*o2hCsp3|=zyhQ-MM&0i;7 zoh1JUq(EE01Tv?(eY>VH+qUHw^ClBJ@+?4X2&~(mGaD!X*n}qW>osliQ zvZsM)v?v~F@}S~vbkMK8AO$6PLb=T4y5^;BqPuC^gBmA+I$=(WQ1GD{QAj#~r*Xg@ z)fl+1YgvXtIk~S_!+xVlThJ`Ho~Q1!wB`%O#QxB{K23m!yv_qgR%p|{4rq*#224J&eP`q z04Q>ABm;NqjBPeqTr)Y|uEBWooAN1r*iF%&XiazR**g{Y+;fL2lJJCq{e^6su~gRLakc3I!)YRuu3?t85Bm#1V2>9UeQ zEBZr${5yW7*x3Ygg6P)Wd5p}a01r4@s(4J@sgkT6@@}PgIW^dDPy6#QW6pmlI zE{mH=Zf&7J`igQTyTm6AqR_mfwQR3)qhZ>y!y`%23M&Zp;2l=1tm_XA*~1**4pAjw z{1@=oV~vS8tn~h)qWE72;&Czl8SZ<5aBr?)>=DGo#BLA6gAc_!4#TNK7vXWgnD|GS zZYM*~ru$@L*#*7~|hAML!Yn__$NHb`ZR5Pld;3ALcADyl>^P{4dYpm{@p?cf=2h#e6r& zd@w!ZqQ+qUBZ}-?5jdGZfW8;_fXB>n$ns^ZFU176w3HF3Z}`nCEmskF%YBWEj)tHz}eZZkgAj zZV%<=iTx9EZ5K{0zI%O;Xs`hg>o}Vsg}6?1!t?IyA%-qF_f90J=F~HLuDsALR$Ww; z6yB4^8bL+cPhD}Y5JnVtS@sJ9%Mk4A_>b{_jPWv;{{SzA;rfra?z8*@ej_KsV_ai; zukG%eiSSRr_)}&9iS#)?_+)=K@i^qY;QnLv75pW?7Sc{D+~z-%4`1DX5^uyfzvE5A ziM!j`d=t+W zS-Q_K1>6+w3E2_3lroTc6_+oSzLX52_kAe1dRAs^2I-f+Aq_Le#p28DUyy`$~RV5wL8-4+&SUjYRxR7lUpeo#U_E(c)|W2%wLFp*u~o~7ZcBn z^DZbjuuoLyd^4I0htwo{`=aN#zY*7Sk2UBQSbr0TfX9{}Nh=fLSUeZG#MsPFC4vva zvkBR9zYssv-jJJ!+$Nl^1`fqzd{ginhn)IzWi+;`l;{VYJVx#lb8bcH;}L(QU&!U+JAb8lw^?U2`g>C= zcPlO#2PlYHc8$N z@f7jiKjIa+0LY`)nL(j> z2+B_AF`{tAp#~2~*);y$mBg3-0ELyiI^{I-O=G=A@|xD{ESw<7QvnX;NIsGKp#z$k zkd2{St8!OHFH2p^&APg@tMD&a;o6WCAzAL4g#|2yNJl|k>pG0=rV**3&@@Jm2>q6J z`b_~quXU9;cTOFU5Nx(9Vy!waNb0v!M$mp;+>Gp0a^_HO6lHHEe<2reQ_GN7OvHBX z5>`>V@S4!?%!WnbGtxhGAi+()94)gT)M>goa)@TbaR501; z8~0h8^&lI(qZ@KV3ImSrUQew!H>K87Xx(j>{>pzv2w}~_0lRDhA>6k|6n%mu-hqwS zDLa*JN@+7j#QQHc{T5R#9jWSD)N7i8E9`-%dW0Z*C?9p~ikcbG??|k0Awnmftf~SX z6m(HoMMi*BsG&941rz4zR2xtUO>)iAKABA2Mbx4qfY2?L>SzyD<>a!(*M!ek)K&b= zQHSnm2kM+hrDuPne(1k((HFFNjiaKK<=r~gQtwFJdO>GJ;xm3-l%8m{yB*g>3DY~1(HCy_dVQ1*e5l+a=zisReBJ6toO4;-vsa0@2=nH% zJ7(OYhz$N;bY*#r7i_W)LXIu=LQ%?LU0Fm*8H$E^S6W!B)(}?tQ$VE^nQg(HcjkcP zpis*9sZ;tRrs>s$bVX-%;$V8l>|F}Ij*xj!JEbF3A}ZFRT43(8I_~7Jy)roYoqBj} z^?gNo57`W(%R={@xvUpelzNKtnH>rlY}#gfpuc67C!#n^Kk_OuJk9;roAg1UIoNk^ zWt4xY{rfD=Z=7sYlA0}>ct;cKHfom-Fxal7uPMwxx)^Q#?&ts@DJBb#l^@RU*=#Ov z-CV&tAi?)QLOE;Nl1zmqd0++3X}qfy(R!BaWwPIZG}~nqfU{QAicMf?D=0#yo#=Z~ z5MAjemgJmN>+eEo{>!?6bdMvlYDb_e34k1l%s1)%`2CL?ZLaI!?%{{RS(WfG@F*OWk8QWnd~7V7$XaH5k)K}v9@$i*k00L=|P$eLwP0zFix zQabK|HRjn9%@ZmDy+>7bWwNnSX|$=8A0Sj~2cCd}4d|?)6ef|Vni7hdO#q~d z8V<@&H4>+xMwhbLT~6yUuVpJKJ|O5<40T$IXdijsVzb@@qIN2Di4%3o%;b><6n(e-p$Te;qa#ao&^7jnBS zprfw(tvq8$))Y`uXjH`srMM=gLWEHiI$?8G1eGK$*2oYg7Y5~u0H7!og)DM|_;{)u zKIp)eF=gOl-KS|;u(s%;ffn^s&XN?39?C@t=!2;VsG!_N{nYMVwHnrLiDF&oZ^DHYB=Qyetjl%UfYT_XovL&VGNA`9 z#YUM?Dg>Xh=)Bu23i)}WdD3eX@V+C=&Zy$Fg2x0}>w>&Y7wm)lNIrjVn78ZA*+MmM2E8h09DND{AV8PE#vrfZjWo!YFOg@{NGQ0RE>rIzfT z!^qIJ4#=$I*2^CC6_xITbVVimHB7HRnXAvbvhds_yLI?(Lk+t&lfAB_`a+hN17(=B zXB#ZV8T5ombQ9Gd&Q;T0bJ1l8VPU}o_eITYI)Rhs3;LX)M>Ui-S`>0pZ7ba!;ZNwH z!k$mnf7Y^A^qNl$)^x`L^?;ymq^r_sGyttg(KUq?lwzLaMYqvq_{}eI5+Llq)^-r@ z=1q1V#$noy<1(O=i8#zOrw>IvJF>&;*{URwdOap%^M;U&vBGto&ytX)9dqc^6|u7Ky!WmgiUg?e&RHU9wO zvBV=$sibR~cciQ6llm*KZE&K1g*E#rDin@IH0d-4N12k+AoX4*6SIm5m`=0}LYb%$ z_d@0sj#vK0L&OlX1ROUBzYx#vRzUPbjg8c!Sw#&n7n^1HZp;wEXWeX_&}^WEKUV$e|FyeZt2Eu|jUPCD2KbIw&n)cqA2IqA-V2(;klh0)rjUB6r0^q13O`jU$b@JrDGDJPfn3CBc2j2vD&=iZqLQ+e z_fU%*0CP=>EH)t(-DV$k;bZxm`-J>CP=xw+D%&EXOi)+7Lh|pji>N~hn*|t`YPzxv ziv^q+=&p;MW`$xLzpVbsc19$cz>D6_;Wc-C}@&-EU#&4?@W%_MGB2@r$nexK#HAY=rqpZ zB}vofl`YLZ)M)ozl%qmI@msrhQ`t(($;+fBylhr#cxJx(0vcwn#(&uoZj0J4Nj{LX zOcm6cIG?hG;kRWVsXW(ZTTE{mwzl4H8DS6z@^tkX-}u7OQ@E_9i#%CX_X zy<#7nCnCRvM+ZUX^gAPb5&Jb<=&huGAqz{DvZWsdJl&mtd|SuBna=%-0D8X6IY( zDI5)vL9`|R09MP&b1w{i*2o@b{4BdD8wC@+GZE~jWmrdASV=pmAc_vk$`!5GM7*eP zlr5&7nO!NkTi2yFUX{;|LKOy?t2AfBVf(pdY4I@^is%W>!(oQ~PO`aI5yg#RPefO9 zQC8^jF-&$|U0Huh02a{y0P>*)6}1kSJ1)vp0EYHnZ`PJ6*ku->P3g~rBa-uM7iA)s z^o`W*(H2b8H#4%m*Z8C{v(;?}p-{7*r9eMr*;rj$Wgn{Y?6V!0l}S=ntG>SU-%P3E z`j{9;KmY6O22Q9Okv4bZW@ZLqE#qgCIYsE5yL`AEf;{?xRT@ zkf2vmg6^d6pq*YT#rZAx62F-Y{5^?M7xi;dOR3d04eFqv;mI@N2H1lB^@62l3VK~n zUMH0w-ku@lvYA#9`IgGwvZ3vc^o8j;P>yuZ+P(U$qm?9n)cnN^+i2zbE!O;5;f*2A zejXgFd_zh0R$zazUVIm~?OY}iAiCqSfQ+YMV)<11!iKi}*O~F`rKfdu*)&!u-FHNk zN+7GJ9R!pVor034g?B(wY66Y`Y@r(`F~c)WiY@wAWKBm1xT4;u;JcQ}>NH5ZO3Tb~ za)B;8tfXZs2EvgW+^L%>B|;-*3dLqT=`x4zvb9|k7hToWtSfyS^iHd2Ja)+Pv&?Jw zXDgg{O()GEKMkYHyJn9^I?LcZp8-&0uWb1fm@orFIk{&h@tJOVv$_k zI5W{S;k4&F^g|iK9TNnm<@JNVWe0t3mV+WHWP2|am}SiBG)o#v%rKn`WDkb#(y>A~ zlBBt13IW>6PbDQ!A-df{xM~3JQ5GEQAS*91m2W6UTPC+E!Cb4gdSEXqx)&6;4s}NZ z#xt7}{3A>}Kf1yz3mkvM&|L+!P{T; z6|z6mNc~n}VioD;U0N3C&dW0#XQGIeE-TAmt}C*aD=Udx70*l_4D{d^=#3b7UzniL zun?h2d-+Y2epe8&E$O;|m;p?6FXcp^vdg(shUk<@;N3=x(llFCBi}(iI6xz!Xf;lV zcj~;lA$qKfb7lnsrER0Cy?4>(64fV~g_WiNSGv6RO#7W zx_04gx#-%Ng$*SZ^V5lpN2bE1yx(eK4w#9|Bt6F2R#sRnOry%tGMvxKO0ZoPHmhlKy6{CZ^ipdI5cbs+w+7V92ZMWPzQ~-06pnPA;dz*A<%=NK2r3!^m#YPR z9V>H70<5l8jW*G@19iy_5z9bR-AMcBU?LprnnUpbDX}?Q`>lytaLzz_E1G;n87JvQ zuMs=0tGzO?BG+yW^umFtl{yVY0n-CcP}6NdSJ`(ePNAr{@NNZ^gkPE7C_a?eHPT49 zUP1xOmSJyRRo!}qnB_w9ciDAY?6*O^6sw6u{ge&Yoc)4;A-yR9U2UgoO)jlX*8EsH zz@%3T3MThb?o+W^A)RP50KBLd30(UtuA@X!bOb`p`Xi!E z2|-v=bp-cnofl9v#XZz~rJJX9vh>2=MZ)rkP`mP>r$C@q>t(i`mek#ETho3dX_`VB zU-5XXca^Zt@{62FTUN>ny_%qGn)PGlR#FhM^6V_SQVz=79Uab#iWecOInqsFDWp;< z{{SoKCsb(m;70<~Csm5yOw!V}R|?7q_pONvPqMOx-kxP+O@(c*b#km`n{lUBQ(ICg zEoYahSbq%I@>nU=dO=Wq6u!v641!%?zSPP}%8LpkOzJd^K~9h=2WrZ`p*ka6)@eLV zv`emZ+|EdDi~UZ}F#`jE=x)CekjTr5)68{P zm|YEzQCLw{;D=?+%{vX=rWiHw@LMj7$~Hu%1w9baI;3cgGgzb}sA!FIqIzqA@*As} zR%u&s2txF~>0PwgLfvno`nTdp=7zgg7sIsvY5gvpihj2jQJ6SlUh4$Ld_Zh^q#f0h z%nPr9_=CIHT7R_avSujyOs8VCnPgWV;t!>ah195C$~x+mH6u)*YYH?^0B98&BZt)R z#VuX5Z(jN(TV>sDwpZeAX}8D_cu`M>clAf=Av+BHCoLI|gg|>tg`eW`heS6huu=+k zRq#I!eNAC`{^O!nTDRk~`i|AccRW|+ANl&M)|Y>UJt^CjCroIaMv{jF3X!aE4u}-G zuu;MqHl>2OR$DHL(ypyrl@#*QN{$WqgG^HT2-Q50pe?Z%37^Dphm#lmPw94vYm~Y1 zhll2Wvc!8NK4GmRl7qb@(3<054^|nU`q$w879hrVd#5qHwo%WXf=?>X&z=>Rh;`rO zJgrwW%GasgdRH?^n(a<*RuHbYTU$iylqozbsn!iAK~1Zfh~E?8@jYx!#`s)eiGuxy zHls#iz2S`}?mUqAUw|2KntaYbTJdl{3*wu*dZX~aABHggBbFkEiyfWF>hO-(t%Zq# zewQpJaxS_#Oyq7=oAjjetFP+g&2PiC8|_#s+i(H&^;uv0BZq{*8`q!AUQ_~9x$SC( z-Ak6@mJ8Ec6>?R-#dlCCvbm=7xQ47S!^5OpgmEn@o^Sp9X#=4)Aj=E%O^EP{~-zPNnXtKGdo>aDBLu{pgr*(4+ znO4wiB;K;2Kv79kq}8a3Mvy*4o9wfS_*nR38J*F__-Vn!>D6W_gE0aARDKWR9p(q{ z@j5T{{vR(~>T&)%4F3R570Lq#0m0L`cIe_e)z_klEgY+P5;qCXAIlbvoLY&^;^J~V zf+^ey;Lie04FZ}1=8>34TQBAdVYBz*Wh!|czDg-|Z5J?9H2%s?)$)~hFEh%viHTEK zKqx`FO=7a9lhGNWKrG73ZVR{ob195#MsPgt>PmvUQuXi2e>sP~&m9WdR5iJs+mg*8 z$My;uPF)sT)(+ePGJXDgnTwOdYi(3j{g7`c+$cxIt?21Q~`sLSNOzPcDlzM`fR%4)yoo8bY$31eLnxpdd#jW#PmbL1r83%oWav zkG`JeORPA%((<;I$bHm@3S6%$%u2PD%&zLeOQ|zS&3zD~M6UInaZhD+6!y{Hv%$3u zA`N5(>8F;x7nk)`O~iN*;H0klH(hFeWN9n9%@$KMIE>q-m^=Ui?4wTn9oF>nvQ$OX zxYh-f1$DJOO7dx!dKXH%lnCib(e1;UN`*Y>?x$3*YM9d+0yKpNlDDkV_n_bz<3|im ziA2mS(x%JZW}Vb(l^VOK7c0LDdT*k{uQ8vx7X25bt*dcg^;f8`QCB9pYa*TX0l^9d zeW?pA0*w(;bP>+5yD30@t98*pAgQL(5Qj)QFQRDycT#l8`9fU^*Itdgr~nE~s6ejh zbmgxtCtR9$QN^&OnWoNL@TZJxWD}gCj;!kjvA`mLuKIUVOX-~(D{x0m13=L_qKDqL zvYI)K%3AKBW_g=_oCyflD>TgSvd_mz z(g)62PLrZ&56Q{^#*^1vA}L{`C{k%1Dl|#p-n*`}vfED7wItVSchG9cqftN#5TP{j zg&-->le+}wqB`Mrt50X zjnoboy+r~#VNX;IPYT>tyzR{#d^@q%J(gzu$4xTPZ8*4m#66~yT&g(n!ZM2UIYW35 zcyOYYLb;SuO)tZ_wJxplfL0JSoiJ1=;KGW{GL?JMLXAD?{H{IN>jX1j2%p(C*k!&O z=PVuwQOihwUKH`5ZjW_#gx0^|+jG%kWB@lYh|?TfD{FzHzXH|YRGudMIoCW0)dfMO zW~g@xLjM5W!zi^)eW@A&)0FSYd4k;$@_p$w^5QsqACt=71f4tUg|t%IH{k8^CZzF# zHJSo6oluLST1CPy0t1fA?^uL*JIau? zxP;PlMIvi{1k{2RuC8rEsD&y=0u5(L6&i0#@NL7)ep<@v%KE3D$n8N0=nd+kheTAPMOg^A!T~oOlUe_e2#7m z@>3&JQVJkygd=`@amByO+kq+qo)jqO!JZZPnYkLrE+vrSKvz&cZ@RvL#=S0M=3z(3 zPY!-SQaLCbVtCS4>&vpf{3|D8zi72Gv?m76c->`tNdEv}zs$;>0pV|dkd0BQ%KTHu zvW>V%#bU=5jB@cxuQ9#LdbO4L^#0m675P&F-$P^HETz zjWnHB`3s5-1>(1%Bk2-;N;pz=`T;#rf5do?e`M#e!I(|xZoE7OW^&K*SfAP!Plsf8 zRynaweU*)EwUwU@=t7wD+TRDxC=4Nh-!^Lh_DNS_jWjhFw z<>SJQc1;rx{{Ra!a+yu>0O$ANWzJ)ysAduII?CoskrUMxwohLF03nsSBjp12)oujq zgIVG3uktq0G`@*DuZNoky5+m9S2r2L9z;!-iU9HO_dp?-OL>IXpHr9#q?*aB@F!MV zeaaYkZOr-I2RYfCve+o_+~K~;+&O6>WiED3lCp!8c2mWYp}e?pz@MC*bE**G#=l90 z;o(I0J17|JFX+D+u~y;{<;3*;t0!d?E+$F2PQoVGjs4a$Iy!>fqRYDPq9a6e;#oP* zaAmhBmR+Ns!Fet@ET!h%(I69-f0XU2RjGa!emv^ihZ*H22`{peFd>xtzkxS>WJ( zEj<=YG2yqFJk{J!vc&>?!zds3SxAb3t_KDNn{j(AqksoxKPP&~q>uJ8%Dv7bDcM_U zHKQ0}2DZ=+tf%d$>5gg^$nl0ABi#+!A$dVP+@Up(K4P<%Nj!USuB}_0aG`hLM@|*^ z-RU}S#GWwks}te8(*3(}wnl6J0G3^qmFWuFETe%GmA}Z(2IiI&xz%nFrN7vsl84ES z9@;15dvRb=)SVkEQuK{8tSRU6xBmcQid|dgVw3W-qkb*87SjPnn9_c5{Ol=Ng)K|g zK2|l!R^Z9u0j7A9OX`~Ms#I`uaBi)~ol#ihN)8;9id+4wZ=bS(ru+#so(LRR`BBD= z5%c%gZBxeE=evBkM!Rth4+3jH?G_*G+kdr5Lb;C|sQLKfTmJw{z(EBR--jGH;g6fz zgZ}_V&XYn=Q(ynY04ERu00II60s;a80|5a50000101+WEK~WH4ae#0P$2#)2OD}a;}>S%0`)r`oqAMljM-a39}r2hcIvgxSp zIX%LIf%rie15RGMnC!EsQwk{Cr);MRTFU4pPhay0uix3~T3W3C0BBv8IlD&}OuLGb zhT&Jkr_tQBV7`xWp0`v}vumGFHE)xzQE3#cIOz*I;N`?t@8?K!WmESuK(^D4r8Fjv zh#_rUdUq9*XD<7wiol&Z;^BF=0=Jl|?1%D4(cu0_qte#k+@{4rJMZx~%s2hNaoPrY zp&ke;mp{Z1(GGnKKviO?PGyvLr{ga}oh%#e0S!_*1EQ)8&T(IrWR# z_XWchQ~v;8`Mdcq z_?4Ba(E1_{cNGO%65TFlKM-cLhiW76Uf{9j)}O*Pta6)~VRy+Mi=L1nWb4r`JJR%+ zfdI3&{<+xCljao1BY7dL3LMn;nf{;!!Tx-U28W(eg#Z`ib5k#*?;D?U{cH$;9Im z4SR=3F3d!%T*1Q^0d$Fwga){j-5X;gJ$B$P;*fvk#5{nAN1z69hFaRw%W@;+Yz20Dew|$cPCq+N|L9dqO0JmPw z*WTaN8XpP_D1oB zWBXuKGQ3xDLtz{?Gq~%hF*1u=k7y=Pw@zKlgkw#AC}Inv=4~1Uee*5Ku%V;4FrsvR z$+%X|{V{;wBh;fUs#1kdy~Lqg{I}|7LA}cNE%LSrk-!0qGl%sNg2{7VQ63jy9#WJ3 z2Z>F#mDf_wme}dkco>@Y_?ie9rir+>93z1HuAo@#v$NE##;MwRk`L{G0ZNNZ{sx}; zmx8XE#8R}lK9xB!M9lsmFkZ{iG>Fw> z{{XSN@?s7ih~&k}yxr6rLvTeDvY^~CBbk(SaC&AmY$1O$2BQG(eoLR0^g za9to!LmHVwa3c+3DBHQa$t~hUkj^Ts$?gW+;%#imu3{Sv%sq%yFLcJxCJ&Npo>{q= z_9dcVp7B3L;hc0h9-bbgps8lR5osP+-0(|n%gp0+#8>G8AXRFj7NszP`Gk}*%lR3E zJq4+DZZs<4IN%c3VGNEYZO8FS7WBXg{{Y*=q?{(DlRM4)#uxhtM(!sMY{=UJpGb3l zU&O&r3qN^(Fr}*BL!Z?D09&M5 zgrI`dAI$#%sgjwZ{{SRFHCS|jr((6!{{X9jIzGZTrj?hz3E9(k5YLQk-1DR*l|s;98G_+b$7T3VsOiOzkvG2FRJLW&_>5z~ZB9Ii2PdwMfdSWW z{FCPQG2@I%!`uj#@XXe;3t_KBM!1=t678L`r8`P7XB@;z zY9Xw3`9XLhCr1(cs?=}PL`+3Fv>6>H^v20` zXboN^VJL6I+;NusmkDR+hnpDaC9-mH2b^U1%dcj&T*a!;%D5$>gGtOix?gd@oH!~! zv@hyB*ocQm5jNutsbm1S#7(lL)yxY3Y&a82e_9LFX=~lwNClQ3Y%lbhK)0{{T+8$~ z=!zKV{CvjhWB3lGTP=>fBA!cSg3K4M%w}=$_VowoSa=?$OFd?b;~(Z*p=MCnXl=@0d^2<=qZ%6R`r}Lbidqeyo?{(S~OuZnBu#? z)N{~v{G$9>VJ&jzeM(Af$=G!l&VWFo#^2oN01HdF-C1)Q2JLfDZoVf6=LX-wm{aJ3 zEaF~Tm&qF8m;V46wk{OnJ~MOWE)8pzU|Oxr2!c0=VGd$Zlx%Jz2QX#}s8e^uLgfd0 zRNf9S!)>r|aEPXL8|)BtWvk_k*nwLbrfEQ1UWoSC{1Zy8h?4350I`{_ZskaA zzb{hrNWiI|gHZtGbjnh*{Sw}|cfe=p`SmN?0cALxbh+*oa)whb3r}&&MiwHmk>zw9 z&RDCJ_Y7bfIp~dIiH@kA-6b6KM?sO8LxzovYy}EEy~hZ)DWU2kXc};h>sdZ)n9FKA zUvj_%7#)x(9p=Q!FktBgXpGpaRewvAgiFuS5@MV?nDtnb1v5?rjSrn7Fjv`VpX2z>P&7@hC*o=g%I%r)Ks>DTLNIV zoyAMG9`JAGGm0s^CQiLdCI+WeKsjUKpry7iAB02*Ho$V*QC>k9)48w}dG`ZW0_Ty< zYabDj7^=@djV6GqmR%|L_>W0EwFd$l-O6Wb0}Vz#A-7@XBI|m z?Gq@hmJh0C5CwzIf&L>6iZ>ar9k44<8`^&63i*>&{?hibq`9NpHX(o1!Z=j3?e!Lt zj>9oQ)Kn>(l`9sco#B2E%Inf(l@EuVvx&xjv;P1u^)Uk#!(=ok}+v3s=dAwe=-bM&Ev=_2?ap zcNA{Oij;^tCWl(TagqYFUZnv|PG2&Cb#rl$*BI^p0IpnM&F$)0-IQgPfAFLWFAoqv zCKTZ}4%FhN@&0D&7w|#~)^E3oUfsVWUR=cDH4Q;a)Tl&Vy+Xz2Jx0PB4=Ccs_w1J% zC~q0>pn{Xe?Un5xsET|l9GIaT9GZnR1mH!BR$H9IDwM5#Cc38Y~W?iC6=SnD9Fi-ZK&BqNETEe3|je2bJTJvh96PR^}B;M zGz>z78+SYRGzs~elqxhQxz!HZB`j!|66f<=K^E^MVZhOp;MLcwk5!^x*?~qX(^UBC zbm5n*8#`3A)tk#>_VIY{h75WS0olwP2H$G*WA_jqZy4!=R<<}k<6eb*BS1R=p#lKU zxIRH1u3MCC7O3Hxd5=fA{sh1$d>GhU-u*J@2%9_=PgYX;k8*=`1|vB;+&Ua7n0!zC z?z}Qf3+1=Cy$L{^jgpmm&Bm+KsX{TMHF{y7Gh(-Ly$>tgO?-b4jH+S%%PtQNnTtmB zU*>IRX;fK__Mv0(4s{Q-w^5U*MG zQh^A_Z1p~yZYFW8Y~C503oKE`phmcF!OiX99@=M-< zHtjRrmprpkFVels2H}GTdK79|yr=!5ecknZt;sE$-!#`8)1u%Phqc(s_3;DPH@R7ZBlzWAbJe zbqt1Yz41FU2%>8-YAp7J5-wscZmd|SY1(QcrdUzlW=R#_GM4xR!Sk6UL9ixY10J(w zPv-%vKBWTFNxgRoWug_6v|CI}EX<{maY zz@Ux9;mq!7pV(*ck&)bWuKp$2gO5_g{{Td7M-S3P#N*=o7+|dXO=*S&Rgow)s(*x3 zX8<+7>IH^xu?_=f&ijrbUx8EgRzLfVn|8%L7IQA{{S7z;qe|K z<#r#$+AWx!SHK>o=-_uTdzSST)aHUSFKgTYMWG>TQL|ml4xo5<6BbR>03p~ME?DQz z`;??)JaN0!F>B4#xpw(Rw0)5+>Gv-Ceb2dud7GUhls(F~qAh6RT}avLdVyfMIe{+3 zFLN1ZQsE1)a;P^la|dS;XmFMniMRf5v!+*DP zgO8E}m_I~uR-n1G7Ic;tef0%4AG%Z44rY2rg?7)(D-DwJ#)U4_B+i5RNkA!7`ks~N zsZ%X9jNG=-xA=+f-p?V_Kr26_ZA&Y0AKoofgi51|$DuMqby$V0I=&v`t3R16TPDv@ zrS8rc&M?Km7A+Z@SQ_o_Qty(9T4*SM$qVDS*eD-}*mdqS;?>_%GJO%1cWS3IE1+-NadB{=fC1d);DKq~ zt{ryHY)PgV6*6$0hV2Fo*p~<^Yf`i;QT_sDIb%h}KNAuaqh>#Tko$G2B6FJ1%XSgMV`;ws6b5@>Q|He!HcfYsk0aAQw^AIT)NHv5!4GP zrlsUnfb!;9*@hs6_>z>_V`Loz?t7aVG1-L;?Cllu<^ItdGKJLc{UQx@5yxmk*V*55 zI_+*#uq@)7`cy6w-msY|>r@2RsDIdLwESl1A8LI%PM%}!h&tSB@k zy|XH>F=3+N>MzolH|05()`m6I3>8+fk^$yyt;aPlWE}qhnJvRe>Q^To*wQchCmp3G zm>ty1q*%{csE%%7tdnW#EDe3UE3z0)J|QaQ-dsVynM|dKW&pA0ao}?e<1igDV9RM8 z2P|R;N(bU@t|mcJP(6Kv$&he|>v{h`;F@ zl!2E{qM#G9<|5cxc&$CP#8%x0Ugg14u;=DGc8Im00=u}CoMy(;YrLnqczTO-H_F9* zB6K!Lu;h2~Bde_6mT(>b^hJ=th->D`pm}%HMK^~XN|M?~j>xhcU!h8VQ^IMh2-g%wSw_qfi(vO*W4N z`WBhOW#`mn=uMDkz|*J;pTw>jCL+0bjaNH!XQGU%;3J`IrX3h2!m2MUo6aoglkq;xpSW8 z&`XPl+_IVCDr{T_jtp#&IfXZ^L3T=CvjZoFRQ@Gn71s%!TPS(VQCPr`%ld^3pOuM8 z`;Sv|P6M3|*XSkdXfI%Q|0m%&^$a41;B{X0`LcFPTUz z;{ITi7G~h6UsC>PrI@N|nCvXEZD+!MWt@n0U(`;;uc=p9`Qz(Rd40iWshReHSL$A!rK&n-+9t&Ol;iTjZ*u_T%rMIWlkJNO6fN#_J7rEc z&x*HQ5jSxi9l$>t96sSne9YDXwKe-fb=Ia{vr_^dEoLSc64Q8NGxjNh*DL{e2=EQ% z09;Nb?K{-$om~?)Kp|y{5ZH$rK--#yU3V^n>KiLiY44@e8&7vtlE!55-s^XAyYCCCnk5HZWAF>3}o=*W2ip9#+@bJr~ zSpCxMuKvk&UZ%yBg?tRlsmw^L6&sD4bErZ!(&gCcGI#DXP#8TSjM+tQ@ayG|3fU~z z>~hQe^KyjFG4&9fk@7g-<_AS1nS}3$`*<|_J+G*|v%T%_X2wk%A@bSmb< z)N@Yargw>mdyQAL&0ubN@|y zKT=plC3;oL#2AKN;?}RzGR}jzIP`j#dbK%c>UJ{Q)J5qpkfn1y=rO3<)WJO%19N62 zVxA$wrJkWOGaC?B!7iv_9wMvlzkU?(SQ%^SAYn;N-5L9wJa%-!b~wy?nNP3vF+Twm zS+ze}p)&Z$`G*U*M6XB+jv*n`sHJleS8zgEUnxdtWgei?(5NhI$58~W(Df~VHsYmw zaya01d?lb@XqQ3wLa25R@hZ_xuK@>u0>79kza4YiVV6~YqnAj0``)Yeax$J z`E_`T{_^^8!IPwP-fhg!bU+K|M^g0A+^YG-;w~n<%XyT1PIAk+n(?a>2gL3d?uRLQ zO~TiigOnVdoKM*(!6@N)ox|c=3m-9sJu+yItGP+0dYMt%sgpa@!okSFm70d9FEcyt zVRY9Oy|eQ%VMTGnG*54T<_^N(X@RX!%}T6((0P9r^)ep$b0W5*#r@NuOto5KGlra< zz&Ej#LK`y3fUZ#1ZN-s|>^W)!kV0P;W?qSvToV$xprv5K(R98OG`BL~0b>1g9?Ce! z6D5vU6#B50DW%hvBRN0=ujwer-qS#BW#(2Va<*NbzcT9QrkZ<;F#RJ0SKEyommjzm zog7A|kZ~}Il#&Lr{{V!1su^7n`3{{**NsWb+_^P^_YBwh-lr-?PcW~sVwtw(_bZ{T zhfL6!3`Qc^=Z}a$Dpsmcr+SVt=&5uvhORS4`GtO^-X`C2*Y_%zbMP|-RCRG^h3N%O zk#ii!QwTJkjwRgfjGY%!3)Ev}$A+xz=-3%=g_evaaL9-1; zp^`#9SQK*bv3Q#w5HiY^u)3*Lf_|i6b}X7B)XI&@#~UT&;Mg2PWZ^cZ*;B%_L|bz% z-*bS?OLBnKyBFE>F`6$l{{V^A44rjYQ*R%K5$W#kk{n~BOFBn)=ja{?NJzs->5WiA zQlw)jk|Q@ja7s;uAyfD%f{29Z`||&}cCPDtZRb4Cv(M+gNoKc19NshiV3WQ^q?!8Sn0g>APR-E~(JNC!9_N zDD=u;luW@|bh}^_6zlIx00vKFlKV>t9)<~vy8ofPu~@dQ{`IFOn+|zG;34vZlSJRW zckKc$*_L#Z)gdVpSM<568R5eA$NtQ#YiuVEu8Dp-z_|yxzYW&Q??+L2OLqHi-RVL6 z7jj|uKa$%^w|fX8VaKkQe4vftc={FjC_c+{EIQBqyS8=pn7b8bcZxftTDilkYbC~z z09A13(zmkSzpReZGCz(ylk2ckeOc|Eu!stVCM=pUJn=KQkmroa60ZZpgj3AJ%##a2 zdP{C#zEvt^3XdIck&O-T7%#OD@cX@SJe?s zYm$_8A+-I%3%9}Lu@$m02wu1PDe0#LebH(Pd#%}jkC_*p6D2&9_S-uA0Ne2ahIsu%gWryW~1{t;yTIYm*Dk3Oh9Kx+FzIcqgC z5WlDGTWsZ7sEC?N0~DW!wezSH$sgq%|4f3v6;Ia49=_kGbh46p8syN;1Wxx0+`nDjAYk$>)1ui2J2y?z$Gl<#i94%t?c)VI)9$pzG& zYUE!c+;okEdUr9`i5dUkBdbXCj!i_%88&|3D41O}GdRoJ!kE2C*P7sf)wT}Z4lfep znb8uR!iMrsbG*qo6^Y=XpH(Rc+w`3r>F|l!_*F5Y`6;GgWVA6$CZk(TEK1-^*-qi- zs|$3n(+)%G7pbkfB37AzbiCVrm%R1hb1GFrx1w)u(1?mgr9`c-cozae^Vvi>TlrF# zx5K=+lQ_hq>08r!|5oz+?2%b!om{7&<5Jn*@g&rLL)PyOFUSQWD zkr@A1*JB8yB%zwa!SX*6AI9dQuoR;K0Il~0-~*@EVODj|w@ff=+(e|^^Pd&xhBa${ zXOx~W%VeLC>0P6a~WT<~qnw7O2qTM~Dd$gM|G*BFpDYdkmNKhb?-c(d^8AbM9{YLaeaPi3RY zC>?oc0q#*bB>$Q@uHXCPFZ0<=;C0%*4SeBiE@3xa;~D96(k_u&KABbI z&4!KV;TZ>-^SwAlaK0NQPDmBI^S#QeV(Mfx$N!6sS+uesJOR5w44PI&rjLtAAsRlP ztQXP+FwYb)d2sla4y48T5!^3F-MyU zxCh`!a){@#?O*_YvP8Q0E#fimuh{!j-?j!^U7Cb{6z``ENG@&+q#JvE+rV17VU8gB zR1cNgv=4ewQ2&A`|JQWKQvB@So=`k@y3<>K=0DBlrhd}*^98Y~rwG#T?tKhogStFP zEi|_AIN;s7B~s6TIQ#%SjCx7ug%5Yty6nbinfCf2B;@6=f}d zERL+k`ijs4>Rl%&JJ-a z6d&fx94GKpFqx#JLGt_Tj}(+$F*4gs@!B#K3H5YA5*CA=Otdr`OiLf`lWUqU3Gy7M|~@so(qLB~6|<^Gt3luIoP zy?G&fb}HwFSJS@H-VBgl*#y=KyYS$))dcC59@dG1Ap?>jh6&i;YqzL-tXa+i@vg5V zueZ^k_BYkuL+y1FHg^#?_3Gc|1V@ff)&qHi2{^x=tj#9$4`~M=r}dI*O`2$nifs0O zva{cTGN@k{Jb~}9@&u3OQ3pT@gSCdsJU$BAf3k1~2zvW5TIP}Fip@h$nYrhCMf08~ z?H%8dHICfbpO{naSQAA>TlXee<_|cS-^}zgTD*pvU$;{D{}b98%D7IbWx)W!Ml4T3 zZLJ=Gt_3L(_WBC%nFFUaxgQmW^Kj;P)|X4P-DjSlgTMt|gN?*nc}U!hZ0`8JtQtkO zRl>sq90%W~KwjYcg!qyhz35(Ld+`6MqGYehA-4%V_8kNb&}EW>eLmOgxOd+sMKf>F z420z(FG@R#w7@+YWk_~38oj;NSi!E02`Bx-9VX92f+j>hNh_c|nfJaiqfh-}#v1uE zcgTRk=?Db)4`-TrN}5r}sRoeu;lTRj?7!B>`BhKGp0aoD(UPYacO(ub&N1UDX?81} zG*?VBY6ctgBFQEwgQxxxM`})!6gm$#1n&T?U-_rqg%EEJauQxM%|eeAkJ@o^!`3}` z^?0}=+PTWuawoUr1Jixdm|xSNo3`?y zcuBj27Q}zAPMn3le#N;8uS8=%?4+xq@q2c>E|p1!>p$ds#%;wP)HB_az0m?2q;1ad z3{IMgkIfE!ZDHdSc+eNadMI}X%Lv%+piJXEF>bKzs`b_G%578`NsV$}MpjF^6b)$_Qd{JoH z1>9%qHgX0#@!~ZH{C4;rNvfUA-pmZXWc*gNET*_tS#&hSLq1w7&+@IBmdMUlXXf@U zbONmswKI>?wZosvlh8iU7Oe$8N6d4MmUS};BGP->|06gR8C+E6SzpLupP8sF@2SG8 zoFxLFvBvdhQq6cxFf4moV&QAgQJqnYuJ@r&{$FFrgO9Dz;%~0=CP{J@nykNys(^zl zKA-#sqSmF0#a+|62g2b#5rKZx`c+<6hV!@($)8ot1h$z`Ju+{%LAG5JHIcpOSiTt< z9z$h{OUN7n*=&$n?vF~`z&HT!H>U^&;r@Pf^YWGo2>Y6)LKV6CSjsDcMF^G@7O66T~T;(D5S(MJ6ruTKs)uwQ6TRo~wLdY7HMz37DSBh>b4Mn2DO z-KetB-+`wvWyi;}D_$($b&P0R-`f!$23bJO{xD-G(qEwpUy4)yxqAA%AUqE46V3{$ zSHI4hzNq+M_T6TV<;wa3vwyg@$kO9VNX)SD1$4B=G+v$^Z-{QtT>M#j$ zaROF@`49mZTQ0HQ&*po3CIfu4SUInfGV39^R?(nNlyjl)E=@hdYkufQ?em%Q=1Y#> zr7mO6_uMiETI=e4z0C3o+DbAR{f*l-$1Rgh^4-2I*!qbm46Brfk!UU5v6_UaTIKyX z$qoAn)c4_6QDvDW_(Yi6Tfp|hhu0cu=9FH{Ay@ZW-7v?Zq_UMq0$B<6&qGJ8yz$ho zFtmfQy<8hXwtkpFd=s@Ldknhpm|(#kVE@);z_f*AXUCFtYPC@{!i8oEDxo+jgA1D% z53F#_>QxdF(aBX_h8xl3>RQvCz%%t~EeBQGH=UzjC(63XWl<2LAnhEP^$P4nY5-}r;BG>Q0G%v=yB-JDWvZ*{6>HiyUarq zRtW&_$Mln5bL*cWTO7fc|A;C5GX)WZyo~Sgezv27qv~eh2y_9b#AofUrXf*04f$lp z>(peMt4~tTRj3iq%-_pQW_m2Y&i0oXF@l}Cf^Jpe>1|lXUFv9mvKZias2`2gN8k2L`7cFT5INM54fSTAX<~h)nr*MZb2s z;Q8{@wgDT-Br5|qN$G$kjWlD{ehOY7ArgVTM|Sr)Pg3Ei5Y3!baKj7^M42?2u6qol zuJY#Kn?4>Fmqodx;Q8<0C-^X*OEqsV)qc=B>LZzfCsg-a6RcOi)gv>yOpWhOkZ8wv z@?fb$k0e{=?AmNk{apjQ-eiv2l~{v*0O##Aet@m;c}^d{)Xiy8$rCQ}uy_VNc1@2Z%18namkcnKl>Kr3GNi8WP5hl1 zN>+{t54|xyp$c_q=C!3?e`ladziI#n+nm)W7*&I=?4Lp+1yrSS>M|&%NSyLH}=M3r9RI8%eL{Dmr z`qEqzq;A(0IF z&2cEPaPmDoAQEV2_tA*1Un&8IS|vD*hLo5-z*ZDi~Y{81Z*4&%Kyb@KgP?uiC4Wk;z3CCCJBhy=IH4O>)ys zO>DBx($N$LG(3KgCWNtpJ5HPCq|1*2Di6@6Sv44j{G6kJVQ)G0 zl;-%xp6DqKc)a4X? zFcjv=ZQl;zh?Bv)!r3UfA$V#XU)QxP{_6}*hJDDg9qfbo)&x^t5;KTk-Aw-sWrVnv z%ZDe?4&MG#sQBuiA=9f+{hK8?^&pXsBRU?}capCTDev(@bb_FzFCY&W`=2sWl6|1P zbKgFe&y4ds0>8cQAsT69AN3>3Bh>9Q%!#GNCqY8a=xzFxZ0@j}tu=wi!|-92Kx9pR zv9E_k;j%U>m}`DE?R&fGi9hWnVx@ti;3xnIX!)<1^`T)_ff8syiOrJv@I;4X16<|m zqu7yeeRI1Dd(r#1ISjb|9rlHvCC;Gj#)vNB3@2L|Z2IJXBr*Rq^*}w0L}(|pC%fcHI;sDWsJ&(dj({m7!6|w(ROVh% zfhHU+Ilv=XTI`ko7bRu7sSvhI?9^wY?w}}aRR5YwuS#}>y%`xj$FH1s&LnV(xAzW1hm&KI$U)YsbNCJ%lsCK|lev_)mn9i_2sg_|2 zgM)k&LkX-m&{L}_zt#?bLXDmxYpzMG9#W$%-d*sR$Ke1%PPX60Vh;Y~1%lTYayW)) zw5sL&EWa`*<_|Y|9JZl~%_cccFvs)`0^`ZF(j*})8M1&nvB~lV{ad?pyZvxi%Nrpl zCP#$g!3kHUyNo_-)=#tXI5nM=X+wT$7q?u1xGdduD}%4L@7D}8bb#gQfkqUru{Gqw zoRX@jG`r2XXwYlCiYt_!=VY(pV?EP(>+>hndY4b}{bPnIM z8RA*iUW@7veuRE9BFg8}L+~&t_KK3MeN&a@^Mq|-U>>+2yAGPcmeTijPkYpw=#bEl zlN^!PWPfu!+0#gLYbwGgX)ajRRq79A$nq=md-DAFV%Mxo^ zKj$>rXep}OXlr5O!tAN-3v9uoMW9~6&2%vp`4C`h!p`hi#2}W{8+YvUz$ZuOIrEeh zLDshfJvr)g3_7%ahGzi54L-5VXk827-E?AGQ3?)u-fGLM(r&cc0n8W1g$J6#@q_f)y?f$rwi7?f@Rrs7m@DfHvWAKR_yT6dAD>KSDpY zbeoACra60x%&`X`&EF-8x|xUd6+pBVVg z75-8wzFUj{UTM{KUKGSQoNRFAY>!5{={sYWBhfdBEMp_6OQWi;rD2m1TxbBlrNuHv z)R^e@L@hH;4asTElS5;Oxy;zGqhzI!_N1Wi8mcCX88D)6%neg9(I1sEi>L|QH0!y; zIGA!!&z0qpn}G|;6eFLB4xu$>|1g|DHbW`<-xPC}5UQDm!)ZmF>wOy`r{E;Yju4cP zBk`-lBEesL2|qhpapXoSM!Cb}X_^tHBD==5b(Ll;7@$~6QNfEPBL7lV&fd)HBlR6+ zixq81nZ`xb(4*3|`JRoH)Az)s|Q@vIIL&xpwc58B1yDQO7X?<((fPbReOyoujbJEF!7-zh^FE@qRM`XX5Y*1@duT zP+?-uDt=pe8d62n%MoA9KKAX?cm9H742d!U2CTfLNINityx5*zerFbn@Ww8Ps%)?_ zsj#;L$Oq9%C;7Q}QjhQ*e)k~}j8PdXgql08)ucuehw^p{<1*U+Fy%|Ojg##Ixwi)bpGW_>wRYMW`J$dY z)-?S4+wA@R5frTRB8nxu2W#&atx*;7_$A^VUg@5ivx@7mO&80})pvsLQ*d6j(%IeC z+wf}w81-u#Q>T}4R12a7FGe@q!rpt$>e`gCvh~oJYX<~@ut)HTa&b@83ap^xHw)SsCy?oWx7B~G1 z(<`1()zI~GdA2NbYALAHq;`AH@^VGVdEXZ4QhwnZ&bSJ$ficuop%$0P2GFSHbi|CeO$UmM1CV?t)F@G#ma0QghCP}oWw}kEiJ@eEfrVsA} zhEGt*yYYL$47bZgI!l2*Mus+ryaZByM$(w#O7>M+^XxGKFdv?E9pG1SO$}4t8O@8( zcs++zKL?quf34x%J>qYn4*A={De*Alu_lE~5zBJ_d}tm}h1SKBtf#^&Cga$3SbUfZ z;>a5j@I}ge(AYrM*&IevQSBm z)~||_=eGPG929?(Q-d_$7MxlV^H_5{@LazuBqh{}IFVOxqE+g~bQKS)+G3aIZBL5X z`n!n1t#q$g8J}`Im`!N3BD<^g5}_n>W%raq$oh)_o&J>MVT##PoAUq|5cgs!@^h12 z%m$^>F7V^->ppfRndvRx=_q5EMh@+w>d!}FNg`~q75L#?R7e%=4YVVlI&e!`wO)&eytMt6t_!dlWS`d6?T9 z2I50@=@U$`Yr;j7QwsAXtXa2#!BG6Tgb?6h3o`X0pFBWM1sX=V6)~Rj)3(32z(LjpTMMlDP-%G8`@^ zepgcf)$O{yF#JGGChNrQNO?fR&VCTW#lW_HDRAAz3Jo$jVkb}~FR#beiR^*1MD`qk zaw!%RB7N!}*qLZ~*>OZBC;yUnwG-C&V}Q~)P$WQ7FlKBU9!E76Xp8QsKP7K^S59fn znSVRlNwe|h1f($Ogtka&k+v!=JMpkq1z1ftK!2a9UZbKxYc#L_epIX)F{ad|P%72B zJLI}9R9ifonbsCsb>t>o{LHULTQu7Aeg_uP8ZOguvn8tn7DY+e1$=3zY!7(hGl)x~ z!^JcDN!qGL)@VYdh&Q))G%Aa>`$&Qf`G6U+qvACFROq%Vq8SkgKcqM0j_3a}mE|_v zGGrHjM$ZS)d`Di5bFLG~`DgNV?;mx6DYhYhkTROVl~(4)JdN~!Bzu2Kc+5Iqba_Nx z=9?WY@FujnB>OGut>6PWM^`=z^64Zqt^J1>b^H1(=;cm2`Dm&$PfX}WaWpjt&-MRE z4prF8W~te^Go_Ei&w*hs-Mhpj;|ghoW{Nu;3#y~uJCn&t5sG2Z%}O}Mn=hbfA2K(+ za_^P3?1%NSdsWo{SpuETYnG`OOFDn5VL2Lma5(w!)1QPt?EON$st6^z2=5aah~np; z_nZ+HQqeG~-mn6ebURH5=H)f=QH>TWk=^#bEPWuMO-Iuk;KoDfiBbjYgg_-Q6fL9yY41bbf#ZrmMR~l%}GTkT9jU()Ohbn zI6rH=ZzrO?J2a6;yHL|R6D77-!izI?z1=T#b*8g(rVwZl?R?h)<)kqjt-#zrH*N5i@5DBI+?~b^fm|Bx`PB z%t9=l2-->SsMY-Qp~L7GAEDrKIdIpSH;vIq~&BcqgVG9r}1y8U}gMmF8Vm^JUA2Riaw&&s4vT4kKW9s8Zb>KXjA_+A|5og z!FGVl@1ThN#V;NDlYR)s#kcBKB;FAs$C`f9gol-$_fg<&z7ES)4r8==MO1>jRP?v5 zKaY7P;5j`ZekBWq6?k3yZb$NgH^LuMg$k06X!pR=eWGy1Qa6R?6?FfRH4X(Kb9UWu zY7`<}>%`_}+pS2Ank<@x=_c=Qe?JE8Pm%dy zUEsSzPf}nNcvaPsHPL(&AuvyUcr@*=IQ>&wwL-y4?%~b+1ImByN;@Ui2&Qn~=EvTi zn~ZoH@S_<6%24^u-u}2cj}SU{q}d@!)`VWLnuSF;!AD;%mPfP`b!zt~Z%A&$E^OYL z=p}h}k9oL+$*alU#h|l&Ms3qoHoYHIy5z<4N&cK_fT~3`Mn|;OpAg$pXO6=tcyDV| zgcLa1T;-<{o{gj*?p@GZby(*(od1ucSljlCcg3$n2LLkle;OJ%jSf=uokadIX0Nhs@hehW7NSM6bJrAv?U$-DVNNylYaw@B+DOM?oI|* z%S;>DAlU($tuC3FdHDsTJoEU4v6j9e%g8A18QlOnjaJEYL3tn!*ZyD=x7HwoAKhONG zpcf^ySaR;1S1{D5FQsDx;1l9x`^{BVlq{GWwNuXoY|y7gRD*I>2oE3MGVW(|Zg~p5 zemL`LvUTESzLtQ*kQ}(MW02?Mwm;(>2;V07f;0>hAo%X;%;xSUt^M0%*Q1z<#YV=# z3u(u@h84#i!2qv}D!++$yu>~C4sz8BJsX-)ad@T=D1z)hsK>z6dV>Cxebs;F$o~0^ z9ViRkzB6<;{Lg94d(W17R^_Q<_CNt5-Luy^)B*l(4CCUH9_1fso-Fb$%3EE&c5IDk!27;1lD)AdwINdp($$v(&46-X)x_a zbFcwguEAMwA+zu;%k;16=pm~eP&!ajW+1*-p84HOpzOPoQiAu}RtjTlXw*`pt@FJX zJ=SZo?9Z!_Rs6=I4+$=9=c?ID_fB+aY>u^SZQmY~w{YT`Bmz(5d+f-pmE6rJZYO8% zvTMd!v3lER&YH!s9GnVvjomfC7GyFP{2*&jG{|!)?3Y2XTW*f}^@kN7`<_vp(!1d> ztV{`_hAl;nzY8F|XghdRO|x1VpWxsaSs-3wBM|1&>$(4ji_ZDSr+wZ`=F%N*K8CksDJLMCMYP)|dI zhD}zl$?3B_F6!nQu5o-=VtCmwE`C=C4c2Efa6{Fu>@)w>aZWXPgBc+>M#%CgP+JcO zZ6AaVdVc`TvS5C@_6+HdggApG}(Ny>&#Ks-gAEo%X6#RykC5gadMb$y2oMQUTzCoXW0>2A{A6 zne``&Y(Z>3%?O7kqc@7z@IM}A!`I*T+aT`@SJFyQT&r}A$dgigZXzI-jMYk6QbhSn1hY*k@D`O7@f zs(m!V&;I^FP1whl*+H}A32jmb3Tg}X2&kGtUu&Cr*j-klZ4bas$u#eSxeGKhc}aRd zw3zM!?RsZ5y^Uh;+1&ABhe9O}gfblCQ#UQQ6!O-3dP)u1@Np->i-tAJGuuVW9iOUR+A@vCaF<#m&3iq-5F&A`qP2VlGbn?JzP zK^dPX?xx_yp7e65kIQ)ua4uETB{~&hb^I>mP7R=Y4*J%dztUfZRa0@9l`$g?M+lLu zYOI_IMlC9=vOJgonO8}*&9FN6tLXuC8%GGF9Ww3-jcqJWeeQJL1+_ zI+D*uDhTV;;TI9^;P$wEq`v=!@_3x2@yv|VY5sQ@IZyFH6M><0k7A^wb}FakEx$x~ z6c!abZ6KJqxRG95^g_!VZWQBrp9---WzC)CzH77Grz3lGri^XH<5leCw~kmEb!uza zzoY&~Vxn|TUcCz1Uzf!5djD9BmU}UF$sknpn8VxU16m-VR={OoQ5sp0hoQ+PqXlhY zldVQQn_{u055aq&caUzA9-Yxb+F!b@Xq9)L1NK;{FrDdX;?k);@_D00pq!^-BKcoK zv0{uvcYM~mcV$>p4Av+q4(j>}th-Q8cMKwkU%2w(1#@Bp_#vEG;MJ_7xP7XJ8Ik`? zdc^#~EcXTthmW{0tfuyEva`C+z176)$=EK`n7FxnY~OJLov?gjv8e$i1&Wu^#w@#3 zp8??)4Ae8^$)))I;!g4>Z~pKtHRB`h2B#gO5@6E&lkfn?d3w-(2^np!{p6W)jT7N6 z6jP%MEf4sBUeK9bly-;Ov7JR^@C*a3ac+_FjG4?k+VveT&H;?NlMRc=dw$u*y?v)r&T~NtCs|+)vQ`` zR9{-Wz&T{NjW%mT<75c#iX#HrgazSMEHo=D!dBOc4E+&nX5&Bha5P0Xo?dr1OC-~m zGzU=!f011c>vV=6yVT270))MfxPqo^fM@_|s@Qzmp(5Q<9ASitU9U^p;%Fww($Jdj z4sE@1^BO<$QOpNRH@(kUvkGLqCR7Iqo^6>KAm@LzXul-ay z$BoUf#XQ8`o)43ps@UoV^uyCk+7952lRP5^M{I>;Gx1ZCvBnxx5vLqN3oIQS))}!T ziM(l6G3`B+_9qAckpiajgxkTBI3-t-moL#BkOGmHrCwtL?kEWr4*oIoP(ei6szyzaYnV@CP>aWb z+a(@xy#(ryFo&kHRkH*myZ)FdZ`ceLSnDKmGWy+t!K1{5{?sC_i}OdTflC@rqfjNk zqy&E*-imd%R!Ko66%N^=r>*pw#H69AvJsM}*q8At0zlpA2mzMIh@ATQR<9LfdE3hz zo{CF-7#J20k3~VjS{TJMrWC^gn{PViDvBSE!+8_@Vs03$70B!!mOMIw3NY5*lw1+s zPC1cJyRiPCv~30O2tXkTz0yOqWH+GG(fkp(XcKuASx13B>YQVobSylcqGqp-Kg2(& zvZtd66R4zC>Fzu3_xp(qcA|&EL){Pe$bIQg6~%oZ2V*;{%=gTstZjA$g~2zzXd=Xs z+mnJW@syLvEh}JMNa}*8N5s*mL+AE`cu%dYY`^nV1ycDjM^yAM(0`DpDX-o>{GCD&9D!JQQXcL z+ZhueFKYbJG!cbcG2ITRls$$8a2Ut)ZF{Pam!WiLg<=pp!=kad2D|Sa#bl()L|j|N zg$fZtBKlM)S-&l(+zw}+w%=F(GzwKiX3~j&@1U zh1%RzmOyD_HlSXk?rmbcY!9ZYtXx!{N@wSJff{LT9EaoQ=Ppv+WsRr63Au8VxL*@-KP5o_;>5LU)f_ST(Wk#nc7e*TMlFf^2gc zk2jIRdPlz99Dy8Ttz_$G^yOPCkYgJ8rY9OXEGF?b=}6k8A}QZtHPx(uYP6Qz4xt~% z-|XI-D&Tnlzm+4rdR|Z3nwv*uH)&4hhT%;yTpcwh=aDPIWHQow4`n&{B(9pO$PiN- zv|5(k9bGLKXcpCPQw>C?!8VB4A$!$vq(X$u{-mjnnkse@yNq{!r3U^0Xz*%QG2}M9 zdA63ym+-Y%H79bPL&~AS(Ze{KAe|^c5tqM6X&?hC$OZF5^WK=FW?$&PtS&|CePY#p z=l}Z6c(tt8{Ud4sJrX)?k98(|G0fmS*@ix8eegOV9bp?0ba?vj!Y-;f+lbM$PjKD8 zPJG+frr76L@$T7O0|)+3=veM7H|(+stAc!x&PdE}A~nmm>m0DToC4b63vRM~e#S7} z8NBx;;dI-p%rzI#hIxgk9!=Yem)!Lw3CBB1Y9>@QN|&w>W*x23BS;wEO0qV<-OQn^ zsOk?AGklLUhAiQvoTmzlejZdv?%eCes@)2$pI4wxfRrnN{<9O##x$H$Uuy~#u7|K| z4g-rA)k5eW_PrN2%dgv_gpU-xq~6w=jd@^nQc%Uy%uY%kq3|ryS7nB&;gbS1U|*MI zKv59JiDQEWuSC_{#jA@x*Nt^^L&Jp{O0S1G&s{qPm(#Qbwf!-DUJghY&!zA{M( z2vh~hNsN8Je~Dn!yrivKk7h+Uw&u3iysBXHE(A)=KmPBNLSBF-B_M3=6#@z>x1#x^oW;xOT}h^-l!}HRCcv0Y0RyccPi298Ku_ zuZlWY@~)8vH0CtOFnHy-@N(1YAq%{Sl@kS%iD;M)1T|YU7Mm!Rs#HwJbD_pn0rXbw zRo$lgvP$#$C&t!n?d)8hAfm4(qs;*`3Yx0lqSyi=B+Xso)lNPC!IyPs z5ED!Ta06ZukV$wow~HL#C5zK)zE%BFFfi|_*bi&Y{?;+_k?K2?D(#D}F>oI@Ehdoo?8V#B%=8t1z<6VF?6~4&km&gPezTs`nJk^$ z^OIKShvL5ByOgk)itm7e5}HctsZwY8= z{>+DJwtT_Z&RcA@Gg1p|QXrouLWF0E1>1QRw*@@jJ@!72dUNb_c)uo~)~c$(Yq0&U zuT8f??(6T^Or-dwHDF0?DMyvdzNUZ>&OJ+a{8Z~hVpo$;_S16GT7QJxE zZYl-1<&Nv)RCf*v*^*QwcVs&{7Nd9e;bDu_;$xQ*LT1)*LNhty$1>*hoK8hx!3*2P z$TndI5G9P2K1Z}4Yg8)iLeX`zd`Aq)_UtZ>6s^Su?rn_zZy`>3IAa^qH-6{=ioN#H zeAWzYafBRiuI<oME|!WFO8k3C<-oX6CBo$B1Jtw zy~lhy{z~j)of?dH-x=F@4p7NnBj4Gv#_=odKQ1{W+0pP~6+;zi27_{hX2nmXDV#^r zs-@bHKHfZfo)JzJm#cv>2S{_OK}!$35Q)KF)Ss?zt36Kl5$wIG_n?`XA27K|6rzbw zk*ThgIFc$IZ2tghI3Gs(mO48?Skxzt*#N{A{+_$c?U!%^?}LFmc9xLN+0$|xfIZr$ zPntrsf^M!nlqd(fc_Jn%wjGm{c}FpM771mN=Gdt2h7x#?MOFEzzf*ljPNI+@`z6+I zW|1eA5YsOTuc9>?QTzn&!>1rPE2kgUdzC1WUaj*0y|E~FQ^v(YRL^4fe*FQ$rRR2g z2)2syg&>YRpmi&6^OW_=X%+TPY+?&+!Y=_n-2t=H+XZ)@DJ-&eg+x4V-S2m>0%C5} zlEG!clO+EFBAKcNPMFvu9Vful*2@Ut4aOKzB|#$@$M;ElYw{R_C9V>7!q>52!kF|v zn+^?TSbFlsiWQ8Ckvq4R;fOQ%wu}$XGL|%6wiJ!BhqC=@3&%jW~34H4{vs8T?`rGv~b$b$e*hZM4|yU$}_wZn`?*`i2;8 zgxhH?AIWvf*F>!%Po`{o3i6`w}Msy?242P9?hK zCt!LDGVsYM4fVoq{+WtBM|Hk^>z}8RvV-fyG_MX{I@i#@Ez^+4am)sJ!7HV*$sDXy zJkI$!R9Nl+AI=jCE~Uwv+uz;6i)b4VgF7SKR$dyl$SVJMG}BH}U`#7fW$%0gb|zeP z!6o;sJ(=?}D6JyjSX;IF*d1dtJ)x~u!jRzI0sX#f<~*h&U5?hx9vw3$!-bDcjY~87 zI=0eHF|ZsKLm*+*luc5<6 z*A(kEH6n~~_^~7NC%K<=6^pOP(C^is+u@cO>R)N)}zW5mmaV%4*!^aWD9HCH^RGmO!(&u4NL^i|x9N#}KuD4&t!t@=EAFcC#nj`FwUoFVo zS0^Yo&nQtW_*&1?M9IT-5;-zBzG#d|+JIufh>CKTKkNaq(B)$W3NmH%KtwMF&aUB- ziB7*I{Q*rodoN*sF(v*FTkrf52^}w$Vqr|GJhpwWJE`qgrJgSjFE)Bi^g^u+ly^L~ z>H~^j(qonXRGqU4*4}pRn)q}7mI6OE!hIG7oF{k4yGi*84hV4*sEq(IWDE5e*V>c! zxpL1FyisUz;W6hkHJ>AQ97K$+#t-nAtHw-nmV>^W(y)F_=>3%_oa5OYr&aDlXcLg$ zGzmIpQlbjcG|VrsVhG3`-S$e4r_3{Ag2gU_*-%IZKYZVlQkP34-rILbe^4spd+5xO zIz|d&cA{Rrv$x*zNOsvrL1xm$+K(yn(G>=6#~RaM-xg9WYokAu=GnKvgVtEz)L{D` zNwob3=ch~2W(!?Q{%(2*P0?nTl|!}g99i2nJ0Cow!0TO2XDiN|wwfRhVcYW?P2}|4zrD-K zVfLi}x|=tP@aOM+u(GLhlRw&lu~8Y&JONKCw9-dS4>D~Onw?xikEGW{mxpb;Id`(BO0YttejW+rl4Rc2sDg3C;0zQ|MQ8-*k!OB~r7BEpBK-Jkwoiq=wfY%FOnL!6W&W zZKxdx?TX-(ZRY7a;`DN}F(EMs(Zym6tXbXpNL!{h&yhV~jR>ZkQGPb`+Y zwjiN!_lR$+T?L5VR*QYga(sBt* zOCk%GAu*6LLR-J}1>f_GXr~Xl;aFE1-`mxaIePdZ^`*pq(hbt~@4noB>KV_i%>b(R z$P6LP45l8_Z%xZyJ=v**64kAuq#}flBBT_Aa~gXcl4t!oe);taIKOl6pZOAK3XWOQ z+Nuj>3Q#GX&*wR*5te@DAXiO#nRY?mWyA6t9G^FnO0CBhWf9nZ!Wvv-%lR_MS?Nq> zn`L)NGzR13@92aO4!SJx#ye9T`{g4>9ni7W+4?gsD_<6gI;-b4M0sC#7OINiy-#NYL7)K)HgvZ^B%{RW~Z~ zLxe0{Pn4TX1|Pq`@hC%Wq6~lMt_(teHJID#WR~t7<5>Z__#j0%HpIZem+nyr_QzIm zZenT|ZYUyDIT%-DWJhRFR9euKMUh(KBF_$GyAK7#3F!`!%cKGu0 zP~sbofe>;_yvy6k1>!0RnhpAk72?r)j>V2t(F%pIL2yQV47?A9W|dMIxqJxN?S`WU z1UpT}lDUJ%45r}CQAYWWCTYP2Y=@ZUU`>1Q&RKlU4JX)7P0R~sC!qzwY9Z5^ci_77 zgg2Pe1WdrI1bAgp zS>|V4M`!QG#tgV-Q;Vot`{UH0BTIu#R$`vnFky}OH#3m|)N7VC7V#?wFA+zfE`?Mf zrLm@5LbzNG;^yGxjnHac5WzRh&k0zM%^wTISuHUO_>2t%{8D8IpiV}~wc*nY%7EUX z)jp*-SBpB7PlTz2{z+w^fPKo8YE=w1`BjOz>I~GN%vM}=6O_El1h}h;Md3TkUZUdS z<`Hv8g3`hXmL=$x)=2Insd%_%&s z(>5>>aL4JF290nuRh_abMPzbjR%ON-lIukM!1XvmONK-oA7yo+}sIH zdYwr_UO-dQZzSz5!Qa!kwj#*wr zygBi_EzO2zW@xA~>Mubxu+#A##5*Fda>nDiY=nb(OawCnX)1C=twadD_+B7)2}eFVY6PIp=1Ig3y`xrIun5uGQa9i-C6(jSzzOwAgUgM>x|FD3(g5!+rMyf-$)wNy*yr^^+_ z(37~B49Zp@;&bXc`N5>dfeQoNP7^tBvs3v?AU4}JnMV{9-XVrn1tBoO*5PI!0E9-g z>55~B7vLT)A;iyy6YP|7W7M)6W)MZV5zqd4pWb1!A2WD%{{Z2OiDqO_38&wVnn7}x zC=DU*B9(%cZe^M3h+a{swJ}!|qnWP;o0es=KLfP7H3u9b*5HIcQ)!cPl&f(cP!5sI z6Cy5h#}VNeDy})qW@Ql{a?uu6%(ApKHK~L(H}sAS#;<{H4b5le@!BoM^5!i}mj#9M z7R1Xk>QKSdz*NMd6}oVMxn~|8)OsaP3ka-+V;ME}#I12UBBJ2g99>MBFT^)0%4Oe- zxtg1-t*;sjfGmP_;3G}PD8$WP;IcgXil0#|$B)5264N&_^Wn`J;wr_9Vj|B6bD5ib z5B>~zo!1$LSkw^DxL)Q3*NZOm9?>r*V`AZCGQM+J-m((!~ zBDs-X7t_Y2P2`h}nS`>M@DTwZ^5f3n(y5gxv0mutQUbgBnE{=2?iB0!mLrpwSjOvJliY z;3id@m6m3v5^srSh}8^7a@-aUiAQq~2Yx^Q+5ij#0RRF30{{R35bU6N8VDI|C2Oe+ zngUPZHwqzPR-(Ac`2lKnz2-@lH(l2!0gofO7(*Z!x$YS(U;bH93@?bLo`|Fd6Y@F5 zE`Q2~DX;MX@911e<{CYJ+Gm}L67UY`RJQ?5xd z%g~Pcjb!CfMR(nm=Ij%sxxF?Vx;@5>X6h(8OGcJJb_N1{%q*j=JB7F9>71}|+W_Cz z=cS0WlVvJN6E?A?maZ(VpW}09SG|Zu4^du#l*@C9~EWA(9dQ2PreO!a8 zCy1hdqz*lzhV9GF_=RC-XiL_^=^`%%p6Er)W)_xZw71FaAz**QCUjS$Gc(`Zu*)|4 zAxgq`Mq${dV`e_`p-LCgbY8g3cY(@JAN1}u>Vv`5y$$b6br8aQDzGx`(AU*eiA*c6 z*@5AHC-*WY#r#oGRC=9Hy5rr`2B}^3e30VSx4T0Ac6PRG<}Tn~i|V$m2T247eC}ga z7(3^SCjS5nXrLqXU#9lD$5HlJ@3j*lRj=kK>US#e z)lkMc1mpB@G(=+tXRP$8sqsV)1S9CN&ylp*Sje?wE~Di&x~oLWlbn{l0XL`a#3E&{pQ2zjgB(a^#r7<~-@dLhzvpDHt>sB2t%1oU6O@WHv z8s3QI6afIa%NPSeQL{`Z<3{O&YbLq2>UvrYV&!_562GbOd#TKK6IXl%2=OpYs|8Sx z?4>AkV0|g2L}%W=2h zbO7StX#AumS^_1Crg?fjhhhHmISU`gV1CJc>N#56@lIy6Eymanq9L{1h)J(_tYceE zl{E&l+?bJP_(Y$D+$^D1Xhzp!Yng3m_Z4l_f{H2EKrA%yU|LCdiKx9AZgZzVq{ zGX@jI{{XlJ{HW2?;^mvi4k`UFY!}QqX`)wG#X>4&BJM3~{{Wn@oblkzBVx_#Ya-Ds zM=>JRr4THj5uF!uE8S8J5{2e+IfH})^7po~S2mJyAgK^BO!Wcz>Qw${%N=AN+px&P z;v{9FSTDOT7FUY-@D4wS49@d5LwT2eN({_?Qr~rmctL9oNAuf%dhhP9(cvfCJEqhP5K4%mHfFG*R-DykWJFT*dS;H`F^t0`vwW zQQK_JKt^1c_!JZ5H$^;n*Ms$KX(cNf-?4?_x?I77A?E!156-U= zQko0*J76e6zsApJ6-}sP33bpyi)CfUq6GGmqkiVB4iKT54BbRALTV9>>qxOPFLr1O9H3j&oRp29^pPA(l^NGaw3ESL1>@mf)i#}fhGsCuZA`g*A zYQN{9#K^>$@nV5k=rbRqhc5R<9Sleu+cVdhoZvy_5%*AI{f2sAZHCInz@zz5uV z1x|4)tOafSuvF}Ue!iVE`*WM~5j~5m(_QORK?)hKM?1g+2;U-73H5Hs2c&e!kiMUa z0R=2}cq=<0`aGxoIBnW~w`#@Boi~kBh;Q?{Qr;`cuxy1U~&O zKm_JOf>n8$6Zp!wT*twfIxCjCn97a`COHH^nCe<0_y~WG$b)y^9EsOL0mof? z$X$>B0RO}QC=mex0RRF50s#a90|5a5000315g{=UK~Z6GAc2vgvGBps;qfs4+5iXv z0|5a)5VlZ|lhwtpryTDR{#!$ytq&FsBdl2E%;CvtG>sVmnA~-0Amgk~5T_ojGA>SS zdd?nUbMYcBf2{nh_a(e#km3(;Hs#zP^jI%5o(+~;ZMVo}ExDPkt*kApZI{MbWtLgw z9{K+Cb&*D)A76vJQmm62AO;zS-R)-+5^#`%c`i76vD7$VIdq(xrYCq{7k&Q#Snn<@ zdywVs#p2F4=2``dht2NH;lAvK!`#P#2WJI?g63A%T=3rwwpsJA+Y;Mr;UI8+t^GR_ z=g;@NHcj65%Fl}Z@^-805Qmi&6T z&0ynacjf;8tGiD>FmEhDj%=QbrMpU%=3TSS%-y&2Z^U@pd?4Tt`bOe54?o;wcWary znH@O3#w=_Z;oZ^*ST|1tn|R+lK185{nRz;rBK^i3^#^46a|zl~!bh72-!PA;)XNT= zk!QW?co`Yf?&RN#FCXtW`u_knFK}`Bk#WxK7%ey>jwcM4F8mp`%Wb|ETjO#WcH3>X zJTgJ7JkKuiDWe0t_W-rs_Ajyg%38$A@^@s|ab*a%^L~xHo(}?Bq1;{zKG{UX*M{4y z>x7xXc0-Y3;v;$9ySVI;?p%1YoLM!&)bif_O%P_R=xpA4kBl|a5eLQ~1B(*YcWt)Y zd z2X-(hb7q*ak4V(ThUuMM@WzPE(hcR#^9Nhm9YSOX2e~$03B)_$pVgB7gz(Rf?6dWf zEHA2i1lgThJ0v}LaAGT?)B_&nSn7DUczp1ycuw8RbUTom;kY_HvhWUWAGGk8Iqq@8 zj89?e=RmM|-b<19a)%nUW?=^yM?8_l@h;`}Z#+!A!z*7W>29r;iMNjpzBcvad@bLF z>mzt0F!5xTSMEIN$=sWhCNntf$Xh}jWnYU}vV3G*ZT<6c5lNPebDrmVHpF9=4lOX! z1dnaK8)m%`!|ga@u{b-h52+n?#-tcq;8}b#XRi;P&l}-#?q=rS8|8-Wn1jb&8@s!) z>ShOS+Fv5_Kzoi+H2&v0Izx_J`wNS*Ox6k0%X+y5sq{^;$ZewdSj4(abHzC*<^~?t z@_TdP2ZnVFN40^+t2`b{o_e)I5wU<5c$crz$|?bp8FzX+DIIJu3qf%kZiiiM=I-{Uw=?yJFqb)MmV$w*%t>* znPbuYTQ!zz+9#i7*=QZ#4L&RwOFFq+@Y}Zp&*;lJv&KQZ7bVwG#JQdx{_VZXHs4ZD zUQf}R;2%)XoGvl$b#JMk<&lu=w2gve2>dX|pUAD?6ME@v^Y>JmU*q zr|~5S$yd|gcAQGydXW2<7ZUu;r0>nIdn~wIyO)V=%XOIRn{QDKA>7Nrx%T7yv&WJB zLO5THi0N=_k;uDauO+@4+|KZaq+H3^lHpkJ{RimG1~{^t5zLF20kW4bjAMb9W5z+x z`%7K^ZF6mC6N4>WBT31vj$3KU#n)K`-cxU!^=}fz)ZO^*+qmv#Ab^%|m_u}8N9&#~ zxzzOTeT#4<78a#g`ceW$Tm7 zJdL6N(#Rb^cx2r8k@6hL-D4djING|l4s?sdW9&wqN}jC~CxzL#e0W=lhS<5aGu>H! zQ484r0IO+#-|=bweSDu^bMAk?f4#?zmz!>zA}_xSmwed{5158|IlV?U$wwiUc+VVp zEz3eeMCuUpY1f89XM;do-J6nq#^pJ_u=36!y>Y-H3zXq+X?`c48huZN;^6*ew#zP{ z@XsR%18LS5;39A5{{WWl&-2IqOeewA&G}0Y6XPT9_vcc02sYw&B`Nf@2F1|zHg2>q zcjKs^Jy~RP#ADr{aVAYRabRtr$HPsxi5-*G#kO||?s=Dw1(?LNEiF7`^cEYi*C!{W zac$Qrvxr5xa|vMh81jR_;$-8HW$`7_*#S4= zC#$aIi#!U$-Ok5{G|w!A%Q7bSx%V*jcIHcocJ1SKTW6bg>9CQ>Ek|pRD}-_2rQnW5s!uv)Zx{{q0A>l0xW)H*3cu4+LS&Wbd46tTa-N6OT=|;yW{T7 z@sd1G0hXZqgZstn@Oxj^WyWjr_x8P>;JdPRbf1xv)bZTjbMayQhd(~Qyn=I>edC?+ z_srmEbN>K;dHDA7-?0XE{RC3Izpjkq_Wt7AIFlqmSn$~cW8*y|n~O8X44tuq=#u>R zcO7^pmLu@&A!#)Pc>cP5; zmAvcHeiM`Sg+XiD)IK+~x*}i)VDjI+M|g7{0wj z4leM!ZC6HKEr7f-;kqsGO4;F@APMbx$tAC;gX}S6w-N_X*1MoON@P4CQahrAlohHz&%WN zJ`!9HHkvvJ@aVkHvkwfD{0GK-&#%$a^#FR8;ud5-66OV!>e(<<@VRi2)vn2Rn`GN_ z$4nDtmd8>q0g4iejZQrpFDHD+`=k^ z>~jtmsqwPIE50_)c3Nyb+c^!k;GWM3X%7d7sgsu7#~pb4+!qq;ww4Xdj!a9e0NEXIBpi~ z3(VW@-Ykw&hz5It_9LmCcV^?u2fi{78D+RzY+^4k_2SEyR$1V7TPMQ|m)x!9EV{Pt zeq%Po+=g&Q9Z6tJGi5$yWoe92ZAiJ$T(+$%MHEsvXE0oQwi_aN_qOc(+L(8w_YQHkBR$!i!Jc5!T3al*RZuRY z!DP7H{8??lzAf9Nxw9?ZZVoIaW$25CW94jIeaflj7G;(f6Bt@fTufm!vckgIXMArk z&AD=p?w5F;@@{QN;$Ki?%;Fu@lZbJ;ZP9dbIP(rmb9#p4CPQ{iS9WtJj%3qD;K@Ds z`n|)J>*BLJxIe3A>!yh3#4~%89R9!-b)-Yyaxy6VKHx^ z(0!`4c>N!gvvO4U5&@<0rT>c)Pe; zuvswK47Lu|!RO)A`8Tt`k$M_`96!Uu_Z`98AYO?6=0?KMpEC#$B*1mNX}%cmCafcJCC=r&E3<+CcnKb}|+;mETC z=kMws47iK(u9LL%^Stx-w_NDRN)bR{Tg}2t967c*j}yyh9_7uCb_|Dshexl^+x*TJ zmFi^tA?-APo%IJ2r19N>4zj+bJA4DT9yfe1h~nATi)Il4PW63UJa;EEZgV?>WX^4b zx7@Y17{_oW}~b8U^nX?91;5yuwXvcYARYyiT1T5wEW z=HcL@#AwT(lSYoR-eKZ6lomPS2Yxsfhlm@42*EwwBJ05Vx<|tRagix*TDEV5iEQ5; zn=+y=XVws>Pz|G@<9U)YxwcPtec6F7a9kx@46z&`d|bT1ocPV+G7+33NfPuE!5ljz zY0R=2&Wna#iFzHwoX)(nql=#i_vHhtXw%H%S>RgQ3pcw-b=X6Rhr!L5M?Nn5g5k2u z;UYGnb2W3_i7Ohwea|%U#{=0h_njj=!-lZf)s3^?fGM$;)?DVrxb-}DKQN;m+@cI1 z+)GP`G2YJ497UN=EF4K(NqU&>e4bARF4FODj_tgbbns>6x2;W$430OP@KHa8=P zUC#k$xRKL#d_Bl#f^iNY;$k-OW$z){OFUejW0rf4US=e=5Od(`!0vi{dnIz}jQ_*{ zCJ_Mu0s;a70|fyA0RR91000315g{=_QDG2qfsvuH!SLbH@&DQY2mt~C0Y4D93mKH! z(y{SSakYx={{F;0);aU+!x5enba27f#~mx$QUn!;>(3B{V##^;l~gig=>GozCa>J< z{{T?^CzHi+e}15bX9^id_w`IOs>1C50CE8Fr$e8p zOF1hCUVfw+Qlj&#dAE^DS8+!E{r;GZNK)|epAY5$ z(1c(u-SJq*d;BZ`F=1DGTjsU#2IA(N*^$|S^@G#f$Yg8JlT#qb<-uFmx>Q_bz3anA zR$BR%<;&h)ia5?;O_M%y^3`bA_$6QVPo6#_D~tR4EVOh+ii(Z?q8cLNBFko3nz?g$ zff|IcffaKQH!a-q!;OX6Vwq}JGm38jV2)UfX((bmNW7ksmf)kAM&}o)ad3kN9Q&}9 zybz;^;jrW1{eyvYWH^1MJvwrE{6kVF`oG_(Yr($_^IiV{ZZ0-b=ZA;HQNWF-ul6A~ zJ_t$_C186FAAG?B(Drxl=2{&FDXRY5O5(e3hyCNr8?}6$=x?cxG^BX^TexEY4qA4p zejVZ?We;W6TT~^oiweZml9_4mK5yg5H_f^4j_$c$x3B0UjREfheD zq7-u$%ZhadT)Aq1We})1gEoPgn}V&)BSfyqQ7J`1rdRbVQ5Gs)T>A*-Sd;Mt*#$#! zIc&b)eXvS&PJ!WG#&G0V}LY3ul?G~LaJaORv0AO0_g4KuiAdAi}Rm=VV z03cbdg^hQ9VD1hqIK{Q!n8?r)^UU^(qjlf>fD4_oW1_#aDM?4;v*$l~Qni6wO|Qr1 zQ#{3ctG@tII7uVTZ{fvp4rKs5JYnNMXn>iP9B@~8A7T<9@cG*wFCNh-MI`~D3-(ig zSlVA>OGhzy}8MF{oqPHAwEL_J5 zJqsO0YT&gkD7k>Smk~$T&x4QV4L3HHieSlUY#d8~z=@X<@l!L&0i*LdvqkICE;lPp zz;h_?pNIX45ge29MyjA65AXejELK}_{{Ul(sMFYY`hj@N9BNntPC8fWxrSDn&YImY zNmm!m@}J*mO(KAIeEF8JLkK%^q$;~Ld;@>FjHhP&YXRrSnRx~y&2IY7-XXK_rH+_Yk&*8 zx7x7zE@VN4agDyG(ls_*Jc|6b7|p&SS}IH#q~DQ@e35Vrt<7mpyZL&7d-HGA_7)bo zeErHkV(Ex}z5f8x=7s_w3eRzIjEoEzLez4UT(FKcE_>#9oh2@1EP5L*EXc1)`Uf*} zF<66%LQ+`ADAuCp6@HZTF22is+3A61m{Znt1&-eK+sxAjh&;ff5d(r?GLN+BRnG`l|^NmLJzw~O%sFby_l?=Oh($@t(8fBOd(;NYh3 z&HXnTQkMKy+wHt}Ez7&lFWd1i@F@Afox5L|iExL@KTR3tC}NGh)sx<@!zsJkGyedF z{iR7##PWvz{{U%H0_kt3N9GH8H*eD)7czs*xUMCRZ;xz8V_k79>Hyd7Wj6_V;$e!p znU>&|yLO%6*ly!EX#FK(Yo&C$2pVD zXku!{`T;JGmfrjo_PCK9hsK6mR4m)u1$WeC!#FwceYfUf!^NI^v zi>Y{H6)sxG2&|YeD=@^?VV5umet=4ptW1!6j8s?E1m*i+Zn_IalR9@q^^DAT%m-`OT|M_#36HVGg82? zu4B4`SdS@rm|Ko;L#gdA(8HP11%&~R33rK+LTeZs3@1Iruz`0oNV&Kau_!|^-og50e+w$c3|_{+w+mb`4&Gny++>6W+atYwE*M8* z`L}5f8W`XQcll=?m~pV{`GuQ}j8V(^S&IR>JQ_XYIon$vl=0#`!xi~;?aWF6Wb$+m zE0_)7v0gtV#&@_in`@pc_F-~L6bB7edu_zZH6^v*UFV2s2H9`%@%xNXyKdjN`xzWt z_Kxv#hFgKCq#dzV@f$hfQ{>F@#yAaJJuaAJ^;+C*xI?Ry)y`tpB|wZa%go%WLj)Id z4+)&M;Gn$zKlUT+`pcPaQ@AC9k@OZ=6bx>cj{JYfg=$?aiw{ta<0@rq66MD*qBDw9 zA0kD8S$*CrDz2FmpL3YOkjmeWb~SprSkmdfh&@s&-rgk!mEc{TSgPu6U2UW3bvE~b zusP3f+ABv(a`b$C*r1^>_sX0tZc|kp-Ic5NbK#6_w|-AQFV+T?1%JNlnMmhl{^;BQ zrpF(dQw-~vL5M#7^D$w@AiPWhBQ(f&D8E}(m3W*~sbs?!2IB(eSY*T@2Qfi}RQfo% zNd>^PA%-UsoO3WjBD3>0xbKo=6|F>+CgWD6Fk)gG zg9~MW$^QT$n>2!)8r8QBoagwhHlhHKCcfG{m?-)fe#EgzURlx5eXut~S2 zClFM{OU=c`BNC+mTPjxvm>|E|RvMWgv5Wi8ES^0ZA}c1@Fc??{;^awV-ZZDFM>8>M zVTzicVEssq4K5xl{Fe0$7SHv{T%f_w9Q`u|)!Fo?{fH`T^c{c5+W2UAeEWW8k~BVF z?7^nJ6`=nBWmp36V*Vp(w{MrPXe%ZU>Y33_-mA~|+FiTD#7`|9L^6;pR7t@!s(@QP zpqGhgl^%$xVyNt6nW!0+Z4jqZctop~@d15J8iXqsA$RJ-(bC@hVC>f-GX(*TDujU>C@ zrMiP+b*RIyKf6-?s%pFQ?KIhM-Z&fM_F&6FUj^6ZrK=0ue~C;DYp?ILqCk<`rvCuK zTK@p+LSN@c_u?#5>_qAks$WvU;FWF}bY@u{OpE3yRUmS%nk?;fXuimB{obO%arS)W`VcNSBkduzw!(XAA$^k zjXL|XvgFrz`2Hr5csB~4yhT8{hGBjg$<=UTHwZwbGtLtRX%^;I04au_@0jW}yPZ)Q zCxkq}+^D%!RyssaCJ?OS>kCmARL?y|DqZB`+<(3x7MWI=seXb-S1e`b7gqwAV^AGS z2I2|jf?AJ2yg(5wGL@E3coA0+VrYYHLO9w2q9|2M0T88!Cx~<_#lfI~EE5jW-XUh8 zM-eH##W~Hr%%MlxqvOAr+Z=xVrI)w;P(hz`^&J#<%uBK@nw7yyxEw_7GGbB^xwwx+ z7YvePSpFiz5%`ZukV`~~SeJ1q0yI&Hn?anO;KOAn@AE8O5BJR4E$1x1nQTyuyiB6e zbCP(2aNWx;DNto&5}4Mwwj#0UaE(}(q!UQHMnWrHrPNOo%-UVT9nHCjRJ7(4jiA{% za>y4D>X}~Jlp#ZA?muj}NDP^O9$++8#v027f|?J!s174{1$O`na}YGk@L(E7c!r=N z}dP53BWQ}`YVVE34}X-g~uz9PjKm52k>>1J8WC^oRJ&q-`n z;4HYD!dy(iF3Op%TM5K=j$@#gQ{rC76ES^HYaele#tlp%Ff+_t`XmWvVA5>^GR5;O zbr`gh3u;tFDsH0}WV>>sJU^(EKr7O{F9h5Sz%XVC9)mEM2vJFxE4Wn56_n}qmm8Ur zN>t(uqRepVl&w%w!pTyFMMCWvP{!$dBa)+NwFfNTB65vxRv_^QGi)S-NY)=nQdo0q zGaE%w?hLX-poB$8z3`U(rs25Pp}K@EJr&F`n8B!neEy@9b(GA3q$el{HE+^9;yoK* zek16)m*_xKajSvXrPM;15w7P_bd$V5syoz0(7A&jXtvfPl|`l|;@;RPaIgasgv=ul zqY-lz8zQcyVhkCTY%iFql}DJD0#Ff&b2BCUR1=ucuIYVA$to+j42{gxZ6dA(#g`o7 zQU-l1K84OAGm3*`JFXa}M0GLW5m-TNO3_+`C1j0zR538ViXwvL_+wTK#i@Q`BSjqd z3Sx7Svk(al}gV~8a^!}lo%l3l|vzJ|u(&OJ6UQixcqhbcuMt0rR%6z&Zz zxLM$ZhB`YZZ*wfW>fo&>C6}Z<5Yux6BJMhdX64OJAkD>Ur%{rFOm*0YM^Ns#`i@fTR990WO_3c!jleLwg`t3FOkqqc)I)G76}SNlW(bAZ zcSIG;3mnXahse+7U98S^>%FI=nNf(=mcM62l4)9m`LJnnO=E+oCTP0!_ae``NiBI!? zNMbP>ls3T3pF%Sjj$?l_nS1~vXlpDI87yLm37Ao$B`{gKvMp{k zjwKE10KzE(ULb+Ez=cL7K_JSe_Z0y&i?AiItji>}EE@EvNl%X?vL}gKwFnQsinj@Pk1#hfg#e00o?_<_EP{!1m^w1~ zi#A-ym1-vt!OYHOqz1`i6p_Do8E{C zh>Smv`zkCfO0PiF(KUz(K^s8mjVu+5l)HyB8_-UIHp;0{K7{Co_ox?^C}@ji&!!Sw zB%^aCV-o6Rm6#kQ-k`aNaXg^}#K2Ot6s$H-5d~ZWdxVGb z0&i&R{g38-5Z%VTq0DsrL{iK^wJuSj4Q3Gc0^*t`Wpgbx4@i-Pss^oYVX2e3K(Zy4 zO7wt?u|t^UjLAeGR|?t8aj49TeJc~3PG#b!GSUunDS<5zi=O4tEZlH7NNlK=O-#YI zpxdV~4W7wUqy%0+NOw487O``e;f|N>9KpGHg1YopTxBj`Mt6g#P~necwj=J`Joj7k zEhs$&$jrzxu{Nk;R10OU=6IBl#^v?6JV4YDODidtV3%=r^w(-Hqf}}lH3eFlVjGIK z!700ry+j;hSeI>*t{azgJWLCgGC_EYd6p4h;$1=t+QCM1aC%!R%4AW394;Bf#!@s& z#6VoSWf);BC_Dgi;vEWv{-S~B7M z!&jltA2UG5W}smciFKHwY7J#BS93_kLg2@(2fSMi`x7Q-%yAq_c6~p*+Y!3V`zK-y z+vrZ_-iE4i5eg-YO+#Gjms_sZ5}xY)i{5(EY^{E<||Yw^v+QWs%MCW6PW3P z7@2NgnZxMXXtCc5diTGv5R8!7D!)F4IwZkK8ARN zrE@9}a{A6-iCT@R;}MT@90k4mOZkRaA*Z;*R@jNC6$z>}bu`p)%$k;rNh%06DIb%+ z5i@Ya%}SyyXxO<8vigfmN-i%13<4XnqlDD60hBv}6~2CAkxi)dHc+HMjOC}$Iv`w( z66u*rbj_kx82OetgHa8_?&S~<5n6?cVo3NPjIzWMSz&gT_Wq)U5T{#~-7u5=fIm@E z#x^zMe$P<_8G4}sDq99>0a%nbGG-ZN6VTpb9^wjKdVy`=+yR;pY`8a;T}II596?wm z+zQMSx6wp}-=l*0nt7IGm3fvjs4tqg zgAubfxj~rf48WpcFo{siI)g-W7V#D$(+nlG452O)i-)I9Bhw`_{@Asi zVsgUZg49dN0lds1z9Q|E6o_&}#0g1{pxX!+mM(Dwrdk}OOI*oB?_OhEO52CqIBGhX zVp=WgIjYeWwumfCfcwfLY(WbmQpipq@etG*mK4CF(2y=VmJCHQsqYmt5u_bO5iu~9 ztht0uFeqxmWdm_bICCs#n756ML~01aw#P8%BwmnAL8TZXL#6>k5Ja9LQHf~+YcPiq zX`&2cC(!hZCVK=e1dGiXK)NN4*xSg@UO#)eql~6Gen<8+JmQ`+X9Kf{& zS(j0%QwAY}Gs-2(<_beCORP#vMl;N_6>$hC#6hn}IgQ0(LUT+kl#W`OsI^ckHMF}L zY9}navKZW2b1vojUDRnT5LYvtMy#L_!%k)P+*QH5mr|&!7()ssE;j>dGHw#7l@$R= zLcGKiW+3h&tcguatw5rfx2WBWs}^`M!I^TNV+G91Wn4%WObCg#4NJwc;vLHZiB|xO zcQ+uoDXEJ$GjPI&qFClLb7-lFITElyLgoZaClG8bcP{QYJvoA{{{RSP6$s5HwH{;3 z+xARBkt!Tl`r=V}gb77W;3mGO1Poj^3<4RL;yQ+0`VM0#+NeWwpHC;JnCXOEz<>*e zy&wO?04ERu00II60s;a90RaI40000101+WEK~Z6GfsvuH!O`&H5b+@Y+5iXv0RRC% z5U3;Z8Uq$r5fLzZmnh}~0=z^Ci`x}fpXN{EXVsw|uOEo1yu;9S8IkcA7+#8EWdC7kU{Nil|TeZXg0Abpr zg&00-F;^yoUte~mu0({DJ3sxoEKq^34%}-Pz z@iI%0&>OSO(_ht0cU~=~9~Y0fkek%s1@bJi(d`hz7^UYp~_qEc+iC7#DIB5sC1sbyD&!V`=2A{L7KnZ+BvKQkh9Q2|;V7^m?I z0zuIS5siVa?%`8yAD95aea22qu^VsPuxt;7jL~!6qE+4X5dsI8EiirDuqnQn@loVu zhzPJ#B4C@g5wP=hD-|4hJ#GVDGnuxYKRVrkXpT!3zkDy41;Rl zAj(DCVf_YkQ-S-&SYwgE1iT|8&|h`P90PK)E7*>m8*wRvUWqhO;} zy(}av3oTL48JW%T6BP(6|Q@Tl`1cjcoz0Ol~zL zhE()Fo{waC8VQ4-fJ8lS$A@pnQLt?4Kb77gd!;buar{Mk!?4e0KwV z#~VtAmewlLqVO$z{{T{(sE~LE$MaDon_%L(M>j1MNk8v0gg(QiRUZppWFCQWXkq(S+&slYknoD&<3;P9JTsFptLIus?pI>!PXiJ-9G;SQC>;{+P$9$ zz~MLp9HSAEa*C#>;IQ*%oq@68SkR#B8m24in40iGWcW_NZUY%=n4^H{T7D(8(M-z) zsm+xJqZc>^3a&kFmloYChU>4uoe!M{+L2i|C zPwp%RfN?f|5{g!E=go=n`;OP!$Ww#-u#%@)NSwu+Zx*gybTOmHD^m{@fOgUk&d;w}y=a@Rq@!6*UO5|FiaFPfHX zuwglfj^l7m^ok-&76sM1sM&`Aj6ie0h2f~U!})}50pD@yD1fm8s_rE!A#Nb3RX>zU z8x+h)BOS~Ub=0g>Oia2Sa~K?e#Lxg6j!p#ud`D3&!sQg^8wL=*U_DAzmW7St;^Rk8 z&fv1D#Ocgepc_=0&oBvKH|}hAIF;O@?k0dj?q{7JQ~fTZGg&O-b*|Geq`qp#@m5U- zwGVsg5hVrXdYtocGk?@9q@q_Nj%65BiO+F`QV$7-)=OOB{-x^lXC!$(ptXbClxMAd z!r7pi&Sd!XY@aEfD_9VdPx5O zEd&<)OaA~8^7&3y2nC@{jUIhOb+Yyn`Ql$Mony`*b!e4z7$4`$;sCo*SEdfcNDKHo z<@Ex`bWI18QpIRGF^>lyb66@X$>s(MfbFsJW&B49b;Ev@3vcU%D%;~V@iS>aE(XKn z`tA?d7D9Fx#$VzaeI=xSOXJiEb|=~vJ6wEuh~-fnmf7ZI;qf~qo42e~C7(5O>Am+xC!v{(RyulYOm1c#)6R1w% zf6`G~Tt!{3es}W`JQ8x%bKns8k5Ro9N^JRUfprm8Zm+KuaCj~yH`*T;_Y37xmxl8F zL-Q3#3v6bI=Tf3=E!c3Yfp75|>O2N!Uo5o>wtnCmK0{0v+k>^KRtwdy+y&-S2yH!E zKAD7YR&sDVtkf&`G&P3k@KkKYP72j}oXTn^vfht1YZ9!@Hjq`1kgsxwuD~!qGlxnNN!9bXTKyKRd!Mf;cmU}wi(-2NbFst}n+N1Fa6)9|jbtG+q>$0D6k&A%*m zbB-&~#Au>Qv4SXVTB81oE0*bC7WbPM~2;6N>EcL{)HDXOp`s56urFC6YrO0!joLN#^g zZxY%sHeB3pW4jyHpfLChLKI#UxrrVcaFw#!pWvpCy>&$DzZyU#c z%*w)B%Fy}~9(`v7GF#2(Kg37jd_V`~{-_D$joB#dNqe^|;M@&hp~y1)qbM+5v(SwU z`)B9Oy7)O~+dOFVeNTg|dRi6Viv_lJl z{365N1Hn&?{7ox?)vwq>0*j*41xwfS3AaME?D{{MyO$3q0P(Aiy=q>AYRiyN0}ZXe z)%hiYnnBsx^sPj;=%)*}8*qKfMK#6)N2JfpRK7UhZ$2h))l$oFe++$s0Mn(fnPPp; z&u|5>A~(RV+bC)*y_HZS3DUCOV#+~erFdBJ`9e^9^D?*RNq5bCK-@>T2_SSaD<{Zf zXa%9%z(5_;sZ$>A7e%HnD&A@oJ~H`&Rt*IVbOno~%<3u*x6W!%ADgu z@UIaZl(pg}#{U3v)rm^>8<`WK>cKN$t-gy`#GsEEmM$}7e8v+de(3`3Vg){6VHnE% zJB>2HUo#g|KB59sWdkUJP`a68)FHe@m1+U(7b&hrlwwwbnk9NYT*); z;(Cc?td)F3qH|@LinN)O@VEzwm;qRfpaUaN>6dFOX69KI;83c+Bvhh~CES|OyheiI z%X?uW0i|1%;<*If=~{eZvUW0A<20DWA4g# zaLNd+367UHRzq5}twxV4p&Ba?sJD7c4`$vaUDL<@VOSovY1UEOxJ8aZ>^`R;c)>y=wR$DGk6SaG5k* z#)RPupXxOfGT*uW-OQ5)Rs)Z2i1l4uTsRr=0`j*vkJ2nxP?Rx&(NUJA!0hq97PUR2 z6dm2tW>DqIuw7Z(`1!@)O$Yw~bhQIeZTjCFz06hFPM4FJi-9d*734I0K_dZ$*D}a% zDYXXZ?N{~OYl~Xww!=RrI6p~5xWxH^?91^68xis*$BU~viVOoVZGTYo{nuIFH7@>(m*9tgnHIIf{cxi?MM-u`7NMSh#K{1b7YeY#cZ=dss%UJAh={nb*4vx(+-)yK;62j7zz^nA- zfPiTSEmj>}e8*@`7Q_`_7i>aGl~n@&06hAat_BOd^?hPEDq%+qQR!s{iwTDDUE%nJ zOGQ!1Rzz?Z@0V49~5KM(dF&u4?wYBaB_Ne{@ zJFX^F$bGdl?kr*~_1P>e;}H;&xEzmR%S>-1yD!a+SYH5e;|x2xs32~OhOcx-mni_Y zi8SG05@=c20jgS1Makpz=z#R!iN5F!9#UcDqE$!BUsCqhnArhnrj;5?QtNyo@{+g} z5|fQxA*tdLRj^wCam?_N5=Iz>+AdEp)Px!Ej(tG&Xdj^{MqH!Z0ak`J%ao!ury2Jv zwbkD1Ouc*nYNB)oeB|?>g0mNbH zj+-posLx{r2DPtMwmjS&Ju!z^m1m@ZvKxJ8_?UXC&oKW@i{M>SYXfIOtK*u24%B(6{0Jt9EA3&#;!TWqc^p=K! z+5V|S@M&X+e=*u95kjn2S_pYX=wz%=_{#i8hf_V+>M={~jqp!QT5REMwx?OdMdz1c zZFN!VnWLG{?7raB@@njTZSmF5A8IM%{nSPa1~YrR&_C*zfM5k}ltnV^g>~S^sHcia zjUP>Y^#nS&#rCyNxRtniYSl|M05w-YzU{xLb^IW)LI>SH6F#I=3DM?Mi8LA(1Ib)Q zU@y|D4!!w-i8Q?#YW$@?EV#~404RG~{wMS3g*!QdMb>E97uAdiU7B#N9Y4&bvdo)v zW3E0WDMkwP3J4B?wbl}+;sl$#@MJ%IA5jryc_kGDT!s$iGAz)95!0ze z7^WF{cp*U+DMd^Mk1Rvi7>=twOD$eqM})PxeUx0xY_Ox$8QWF{W5`?-jO5l3*&bQY zp81SX!f8LAgO*x&=9=XW}vW|!#gF6Tf82KN2yY; z#7$sPY#`Mgz2X9@;;7|}QceaVBY0!tSgQC`ZZ0csqd7z>obw*hM+_CHbmr!2C3KTT z+nnf%H1`G9-BR@pf&qt^xD>lQLa_!brr~fPuq$8hP@)uFlOOT%D)3Zv89(1u0|@{~ zZ)QNd(~FGYqJdP(nex0#i}Hd1$qE`vTGg>!-!7VzT zoEW)bwTXf@Yhg3Y$8al$_TCSPZj8{ReMCfQ%{z=HS}%TMv3x09Fo9anFlS{qY;heO zlngRTyMz>ZRiO=6L>F}ve1gkrZ#NI z%_%G(?z7sTxY&6qhoO0vq-6(wUWrXpJ=NL!i?PQRu8))P8eW>tX{X{WcFu%z;&FWm z9-mtGa`-d`E!1RTQ(85UdNuaS$Z@dW1@lqbf~P(HQo_zSPDp(I*? z-fwWGr7WhMFM{#rrbt%n9k97Ge{!wi`rY4`;ElkxOZJmMyEz%t;}`GZA!M_$EmOEb z6Yv2K-&%3Ch2^0;rF}n-FmfAy)vv5qm&CzN5p&bx7NXA`AB_CNy2`V3K>KgvW}wBz za{Tz72u{JUoX#%crJjG?6FfL4Zyz6|P2LpQV_!6xK91<;4@_(@j$&$$txSZ07foVU z+&I*w>l=<|C2Tr9i}w;c)vD~x=pp!H>bjTZn+gq?c^|XkgrTNDDc9%o1bu^yJmbwD zm^9k4u7|oMy5q5EEw6Q7aUDrElWp^jN?S`=Kx6s)h@cr|foc4EiXH_cVTvDgM`|M8 z?5~(1BLdtdzYI*0fm^Nv?g~|n6e(T5vK`7pEh^ykE?8@j_i6ZsdxzgUZ z6FP?T)K(UX&ohPv2`M?S?^M*})xZi@a`BKKyx&Ht!m|Oz_g&Y3J9|cHKM5H{$S$_j8 zcacz;Gg%3^6%IKuEUVrG;$X_ZvpEx@#x(^li|~K>0I!Ws$W#sLwseu~0)GXAOz~UXiL>^Yr>k`;XD;nfPCuNKp>G~mnRQ3_G zk|TA$I^)a(n3yY*)Bd=(&K)>ktDBwBtL41Ge=1%qZ_s8SBD4V^m!-^TXxwpk_?B9X zQ+GT60Cm(<^C>W!_J5d==~dBj5e22O$oPw~ky=aq2(_YsaTAIvX85!HuW_jlfDWCy zhU#{H4u{{LFL1O~p+G0`{l%~@b2F?Ur3T-eW?hi|y$8S40u&fgFjr=n7As2P-M#++ zQ11g^EWi&c{X#0vOTcF0HCbmOD;|Gv-yLjSpXYHG28;$7eiP~#1Lr8b`ZpaUx&v#_ zt_f5LfU6uUw)-KWX-^8pOgj8uEm`Alp9HW zol^qkAxdcIfdO`GFZ!Xm6=e?F_Ykclsoin${^Bojnx>!6GWQv(TtDej-L)`&VVXS( zer7>n@EQCYN&MQwB=u}Vi;VxUdzIwsZyzF~lEPgx0ex>!DCa)qr|2H=-1&s<9w zZIZT1SRxcck*l<8s~xr*EUQU3tt1iD)CLnK<>9?vo35S%v! z_7~1PtIS#j0|T!x?XsB6rnxiV?iWO~GoOF;me&ee>oWQvvCx?uK6`*TW%^H(EX5je z#3g>HVz#>(XiCB4sec=nhdAGe>cvMfRH69y>KdTz;}^y16M)}uFA?E(_^$9swYoW9 zJ;%@>FKRENk8#S0Z5PxrO6K5eR`UJ^GJNs3uBLVo3FK!Sd93gzgG6@U4A9E)va}()AmU$a3Qp{n_yp=5!fc3;oP(6cie6@7zNy z>K!N1>S8Lfu$5N1+_+NZ8D@_={Xq=Qi8WvCMXzcs2eh?`NZ2l;B@<4+Ondg1X|neG zN&w?iS(5ESc>n!D>rOi$46PU|fpD?kZdtjt<~LY&hJn z#2hi3gwrSi-YYRcOtxWCpi$}~N(Hq5C5tIEWT}?G@e;HdL&Fb2Qqu9vB&`acF_30ZQ2m90%(?+@62Wemg=A!9B50zy z&mG4KUoy%Wx@J%@bz}pIY7H^m;%-8)OHom4^BNTGpZuW=q_Yf?g0&Xca8TIt7*f(I z;o9*mkX<=^lXL|rhy#{4*$uL$o2bpxy&OuY4qP>;qf6KEDu*zAN}}BBPpl``kb1D`s45(rWF)-_LWxJA1Sr(^#Y$PN3)sHqIeFz^DU~l0ndUe z*~H+%=)b$_7d|ea8C2qPtVq(kY8+&Wk~kj5J9I_(wtq3Q-gGIT`_IHc?JWZisE-<5 zzZEY;p98OyzlnSps=~XzA*){1DUJ6EK5hqq`2OJ?Dwq7<@+IJ+vVdPa-5)F(YA?cs zWkZIdP_`BwcZ<`U`#hy7(4#c{_!?&fua%PzhR%eE74 z(yo`7Nm`dzSdCaqBO709gwk#mwab6*WhxuvvD)9?;$-5ew%os{!YOj?etdBNFmrL} z{vvX!FxF%q%5Z>keDB=OhG_`x>R;IbC)>G@Na$2OR00WUyFOuC72TVIEI{RmNI1M^ zp-Pyd`hcuiaY5Cg_?eo`%Vy-rMWYgdx=hBpS#vXNs&6nd9IQrg2A=%H5QAu!0y&_7 zYMbs{gy2?XZi@@cxZ(g@^GqDKlhnYmPZ6Ztv3n(8384MPxD-1d5{(zoU>lc9{7g(O zo0;a%s0x|^AyCza`lSQhSBNQALA^_WJxU8!r2y5%kf=%?;{aMH%3-X4%+G90^ya1c zhr|urqc;R?K*XSJ2GKZb^Xeg6{{V~>*`sor2Pf`tiCH|%4cxh|Swb|*!GXP(%)X8l z?DIJ0daJG^$P`8Wf%+nKoTP@J753|1|ll?cN(i5q~bh`z=m&?Ib$_6 zj0-<2lu@9z3U7m=8&*Vy92v~g=~oUQD70jOf{5H>)=sc79+GYhi zA9Cxn$yg@mdJ1J_#@$t7K0z!Ty$@`{-SrT1LrK1TwtaCh1x8hu?Ey8j;@OZm3&|;v zxhGP8Pkx{-9DWnTxPsa~MfyI8zFNAv1*7%z5@9xx-NnrujuKFgXt;MR==vPzUmjw< zyf+zREhC?ae%qSL-9q)0H;I$z2sWXOfr46C7=_3-3Ffl>M5#)!^kwEYE?I8eQ3UdZ zvguUopTsT@S#}(IVn8+=BeTWFp-`q=3+e`mWRk$`?P1if3zn^l`U6Gl66o3v!?)}C zj8mcecFb7=!K~>e6%q>>w09*58aX~-k$#1Ut)zfln2tsOrFT8`4cNw8W#zcG4h>#c z+`rS+hvF}EO1%>D(Dje|1ZfK2zMy8RHrAuGQbCq$exkrBqEtB0HE8DI*pzUooly$g z$(sj~Fqau6h3^bj=PKn!&k!SS(5yHukBijKZbrSvsJPT(7UaaHSSbQhXrTC(9lr#x zwVcpy?x^ratX(m5Q{oBe5V~e-0OR>U3|YbDoRX@rMWcgd)?-#J1y$B%fHYt=h=9d; zZ&53GC6#3Fajp6pMI5`>I+sg&Cz1zO4u;neTawaHnnH>}1%vf6?3#O&QDTMT)V_fg zE&@we$}+)WM~asDP)dN-kN^fuM+S_~h`ZVjL-}R)Y*~EbCMF9U9n4TnEwA@cTUVOL zx|Y!B9{83#0fJM`sZD;Ps{`2`lwyVL;xQ5rdG23wTonQnfs}H@EC39_h^=~!jtNVH zl^BDBDu}OIf z17dI$se2it&E$u=H38bI%mtbpvE0XaqbvUa#Kx)2V8z7H8O!$ydgzEQaXvK-C<;RT zs0;Zbk0wfF0m)aL&^8Wxb z(p61W)P9#~)C5O=@qXn=G#l4GO1LfZSVX-iRCodCleulKfat7l6QIOOK+&c0bM8Evj1rTty4?3Pdc+7Nbam4I05a4l9)O^l!smB| z;FlpsWk1)C5M5G@YD4&_hhE{f=-uWsB|9tmg7Oddxyj!aGc&WgS@;)0^#N;diXW&( zllFe%Er90AxvRpmi53>?Qxj@n+E#=+Mq0MhG~0ldsV1>5?Y28VE!D9yafm2k0` zBE>2MiozgU<`9pXnwpqDEk(V6Q*SdqMy>G@+p4daP_u5Eig>6z!2sQ>xFQh(qU6SK zFdND_JVAVxNpjfQym*(#1ep9rI%#p?Edv41^D|W0tAO&dH=;Hs0yscs{6KS%e*rhE zX^U_4O1jHccW@_g!S_C_!*`wIkxnp!3`IRVZlL$4`hf&2G zurmknbuC9x@k=ay%u;hR5wb9qr>D$#W53vTFcm2GsftK+V-fB)C9cS-wMmi^X`${r zTg>)LHNs8cn^G~MZ_w=P9KYY{}@c1JSKUG-$!-TL;!@lIG12$0v~H;6t;O z-#?p#y>@LJeHMMhE^BwrCzGFuhtXnT2$cXDXdQf|KQKFuYZf?P-{xyDa=n-S+zu`e zjqQAHWra1G%XjKxifTK&i?$!q@ooQL?q-;$9XgFxH>$PfEgj)Z+z^*m-OsMks8EsB zbN3b0KAV1LEe8ouN)gk`?Wjaibb<=VUpbW(zHU)KuE0XVmBwR0=DCdlib2U`n3u1T zIAn{i;4Yc|P^*8RFtt-<0hSuezhe ztu={6cY$;m7S4O%jNnHbf^90P+_@}@zH<#?TDy)5Sz+rE)kBun%dVO$Gd%2~xr!*z zvy2lbcl3GB&mNne(Mq?>5RfKE{yl_RR19@FP+)Bc#;a8SX_gHrT0vZacmN~C%QD*sH z$GJf*C@LNTFzQvsZ-Uv7K88K`g_{z!HeT&E^acZiAng zP+H}3_ZuZEpApa-c{i79>LnteA8w2wIoMJ2uheZ)?0%CG40JX@6mHc)Yk=OqJxoe- zsQ7_m&fsKOQ|4R@Hez4WYLAg|t>ilYbGrj8@`6a5Xjq zntOv+gErm5x3JxSG5M@aHbc^>sa!WR{XejL#h?m#;NZV*oFl6ubzIRuOI@SCOtYcf}Om(fj0mG?S~s?bItw!A_y&Pq;~vc=07nc z8ym7Z4(!z^sf#1ZFGu4)aGQ&nhk&T46bQfgOdKI_f_^G1RfDTgw)<=90qQB9-;@#X zoW7p*G{{)<9WSHBHC4QiYzJjz}CwFfUTYC#*zWx9%0Jk6Fnn7XjAcTHtCBTGm?XER$Jk zH7>yjj|;;ceG7FhU@4cWbwy-E3L7mJQRa~<8l;G22%M3TmK?3ze7g*D<{_04I?&tD zL{P9xO6>>+EXJji-o6md^6rkJzt-uV_NH#?`k}%M497AagaL3S1IrRDh}-5gs)EGhvpm6lo1%#L zWqfdpYu`UN28khi9m|T(2h1WPQBzxB<;9TX~AokmvCfQ0ARK58Mb1)*v7{JW5fI6J{TB87%7Idk@0Gt z`<9&*y`JW8PznvI{##;p1%DOP5KwJ+zl=<7qJrgYyyw}}i7K%i9aG$Rd3k5t6$-^wfo%|NvWb&y zwd*W!9%U4vej~(s2X5dv4CZ(vg<{)$LK}0d;!Q?~qh}B5{E^e1Q2al{!fYTa9r~Cl zXk4QG%WDWNvDa-KMM?%&@OKD+O^K*cMi2@GjQ%0G>>1>MDKZ|;7%4|^!z;!35}b=4 z(*<0^k5axHbMZW{;!}&Q&j(VXgkz4S$mPs?(976hF`!Vw&FQ4K;Z?_RyxtYE*FR2Oixn7`-LM2Pmzgg>L~bM(D#`9$iNQY_7YGLC?D%g^>87wZXj45(5!`sDv^I zuE-#_NRF7l_c3b|x9~;!6tKOxdCVr3!J4kBZ?!--<@tu^9tKx!$~V9z;Qs(;<`m&} zg9V_p7?V|teZc%e-lD0nr-E01mg7~$vhl0`0BS$Nc~_}n^5S#=Oul9!%(+0UX~JNZ zXH_n;ur^F{T9jFq^1f;n7&oZ)T~-=_DKETA6pkXawdXNa8<~fORO4{-HR2*AXSga2 zj#7XHk5D%k`po`%j4A zTffB|%R&RC-*8EKs*OS+;pdrqrwR1{s?i@%7PbpLz?mHFhgZv(nyXf^0IIU}8ib%8 z?mBL?_tZXfNyja}GWI|vyc&$Es?bXfVq(Wt+z+CK^$QGMC504fS|M!xL=4^RFqh*j z#0g^Cd+|c@~DDFc6 z10F}&?t2i6pmJiUJeS@8Iz5eMUJ=q1ktSdMi1Rq$1}6Ne zFWdJjvi9Z$my8ftVI5Rg22~QExHn5!)8ZjpUH2UWhcN47{{V;c5o=E}ELnJ$!wA$X4qxH>zM#^S zN(Aa}TmaBBPjCfGz;kh-)acz}>(}^R*@TXzRMvC%~w)6CCdRC)gZ z?8QTuiDdL$%#?82MS`2Vi=^O)$hN|6FKF=vtC@ICnBcM?>X%;PTZP4}v5Q(cj?|)> z^EHf#Yq%j7Q!ZFF3#crLjdqNgc?r6@b~6AuHbYF&S8!6COg_+Bny4zSK=CR#sEqA3 zF1-fvxHL`L^#FUSa=>hLL5+Z+AZ!^5iq@K;Hz-76uGl7=+Qb!EQoXW(GyrB-YV50) zG}VTp8csAuvc|ICF-6Snf!-?O8{6An;!$S)c^6StZvpOMo%4Ez5DRt8y<>w=tDw#55)E>^#z+OT zOXKsITpf5BV%BXDY|2MC{{Y0R5~nB*R~8leh{$jkqE~2BZ@G6hTWJ>)6d39 z2N3A)DzC)g0qlp9rX>|B%h?&~T?*s&eu;&Y2p8#@IUt4)9;FQxj}AytSy5aC6Zn8s z2D6pI16i(v3+AB&Dci)P+42$lm?;H#_x}KxJY!MY&H(^TAaUGfjg|K;d}z9kXNIDb z!ZiA06-Oz7w|9=S{1bc{?N1PV)P4>w!RzY2AhyRMnq(grQ7s~*b!E&8snNHoMx8x` zKjY@*opgZ=XI6#V;uu0Q!yXm$9ZU?RHDBDh5Zk)GkGS9{m5PlJ4%54ROD+p62@!V1d;{$t*_wt zlone7m2!Sy=s}^9v#(tILJ(0XRr`ELC6N1GN@y|F8}DcW@l@t}YN zdhS_KdT>M#P-#s@I%L)T`iQH+vzKzXvIX;~>r6KmQwR>OVhk-FU>QzNGJpmc^(`F6 zh#*7{V>b?TxHRQp#HvJQDy@e8V6As^d8KwsWt7{vl>&&v2VcPwi3=s^DQn)+EDgd^{v%|+k7?t)8Ul3SO;A;>8q0RFdgQN2Vv88$= zi*xCil2o9KcBgeJfG47we8wfdrN`p^RACM(7BtZK<@T(eqAbu*dm|qM=^$5e3)Mg6 z!2C7vJXFa*>d1^PSL8W_YSwyP{Cy#NSR z5{dR-?l_hyyvh;ZC0KAhvDNJXfMV(m*cLFm_>Ew;2PjI$uT{xn3bxg5&aMo1gq!d2 zEc-<)@I%yr;8~Bs`C==sC+_~G0VWcj$JA3~mee(r6&Wqp)j=qNgMAS0V;JY6eybNO2F3J+cd7^-$q#mV|%gvfE`G8n@i2rP1Jj znPXun)%O=EO4-2!8yyQ}=)8fisMsU6YB1n)izXA`37H>mBS7N;&B7~TfDU~|6oF13 z3{=rqh%Ryk)I$Zk@66p|%Y=ti+*M)h+)BJZ#HABMF4C?xL?EVAR>h-`<{Qu87=cw#b8A`ehR1qE5 z%=w7a;vNZqR)Abwa6oRJ*zF2A9$GZcBL*mlrf?if93EIJw>Xp;-ex#_n&J$B1Ae(E~ZJRTqs1Y+?D z!CRCiosnq_u)3(GrdbW&a*ng0j!-A_18d2T>MaPM$uc$*Rg6OPg#55nIzKSkT39vp z5j<^d9X%t4#imd39cNcCoYToN^42{u7MyMp03j?q0%`oqfE*XcBy=d_*#SAD#BIbeb>ZM;jekv|Bkm4|SUgWW{(hIarmVU9v2TR<8X(TS5C z*>cq^v5`E%K+px%w(C7+P@;X2+~6p4(fq<{GdZq3QA8KXm-53UTG7cW9cdhtuYNu} z!@iVH5Up)>;y4>+!RO)|stYw>rLS{FSTPizhRUcl5C)fYd=Ll=S`Ka*Q~Fr1^Y<3z z7|gs^i0F14O5w04CRg-E0BZvx{{XWKaBH!*&vObK1vJ0ZNv~uFI5?819!x#$V)f}tzg2)W^B3UK}r7gAd( z^DaPL45vY$t!p3i@d{M~E%7MOTIhbI0%@CW0>ex#y{m{tu*=y-I))&~7aBFdY92ug z%XR5CJ`p!BEd^W)S!kg%Ei)`Vld2(c9#~iAZaHccR=C-}8U0KJBCmwz7EZuoTJG@o z7w9m-b$y#A1>x|9iFU`R~i(LKxh4W@-pAAmWY_-Wkw_qbebM)=%3h z%L`27`GigjVrPxG9;L0IM7?TxBMNII5F4ez2Utt&<|?aX!yT;Vu`ZXTP<+sat8bW$ zGsFJ)ozJLYJwAAkK?R6up>^{eMpb?zqGz}(Umig<{Yr2RE1;JVvEjUXmn8z+JjCOC z$;tSPu6ZKP5~eU=`;}9i+sW}PaOHVV^8UM;7iH0_e37e5AWeqXw&kI!-vQhtL&js3 zm7E?cIlqJ@+#6O@^a9#&zY(?84p2+CP?j_h+bTtzrhh~t6>Y2I`}moM*HJtUrB5WI zn~Q^32HvUh{IM)ibDsYIVvb^;JAdQUVCXL%^kT9YbfLDY*qS9!^_*O#EQJx}*zy64RiG z8dWN3moRLk^BMVp!v>nc+0)bqO)m#G)I~AUAqOYwB*LrKSzz}G5W7_m?qVrjl8uyb zK!KsxsbD%ePFO6;Qi!=9GJ%DPd_i@QF5oJSSn&nf7TUmOA>Wv4hK4Z(DhQ;Dx00d^ zF_e2S1u+MdD1x#cq8t^3FD+9Z$xG;t%OODW!D66u@d_(#g@~ZBfvU4nFAdy!MV|wE zu6vom0S=1RBSWKurU%iEglytkl>A4O8m5FlP{tan$>Zh%RkD~Mm9>r-vFL_c*Cs;5 z=L-J-va!LLGP)s}UZ4@oJ_5s-nTmYIZX4VKUoXV+@8FKS?eof2n9Dv_-acbD3TkJt z#821MYDoN|VK!44aI#NYKCkLI#16S70_x@!GoCZ4P-jxV{7N{tO!$t`3SR7pdLm@q zq?tnR)DXzlqF@~}sL)$yEPp5Rf7r2Ga(?PNRYF6;r%xg zs0f3)%YZE{+Ff88pZeO43U&63tYl?v>t}ra5F|bNlppBE8CMbtt zj-It%*YOMs7bM6TIpwxCQY}X{%6Bk^%SML;%2sV#9DKDCaPk}yh!)nBv*&j%S6^9N z3-zg7%wsse`M7%kK`9#cMpe+tX1--gm7&|4fZ8vV!XN{(V$@F9{KN*nh!~vRFd#Yf zl;uS*<~V&Q1OUR_rnMhQjvuLHp?WEmp_ANBIoQgA)cj3tllwQbeo%%cVw9|6Jda`EZI zx2`Mm4%H|O_iZ_KUx;FY-0qDK3!o?vt)cd1V>NcOGQ)Kg)ym8-3a$J>F0%I&Mj-%? zC_>dh#v-p^N(!{y!Gye*a7$Q9i-lHs<|j?0*r6^yiH%nFmMXArAV!MH;!zk`@e+=3 z>D&_C6p*mFyhUZoW$qKi$IP$>g;?ODm)C@Q0t-I3~DuajiQ zg|EUAIt5JKt*gU{e{f>)3mAWE~NQ@DDUB8@AHhwuS$4syf& zK<-Jvj|g}=&*Cq-D?@Q2bAvdQhFnGN9)uDl8LpUMy*zH-3=o_R%1Z@rlxS}BjBL3n zQL{%W?k*tU_=PN|y~|8L35X2c<36E$L0&Ty4j4b)q8LS@&0GB30#H7O>QGb#c|Kj# zv05~01)7@#d(78|i}>^W{w z8oI~03$bcf#4BS68j_dh=O945M?`FMD{)UQ<*bFXQm!uBB_y$At#uh7%R|Q$$2pCY zNtt>DMlz~XYREs9U2Qu7&~m|`^pE`*^fC?*Q7)N&gqot`-Bl90i zv09WXfGF~@I8Zr_yZM#PUfYUgh34hR)@7<<^)t(ta+a47<&u~Zo(6&#DTKr-si& zBaZ#8!g5=1Uf!j@A=XoA4+=miHtFM>BAt*hK4XiLh?6$a$KhWddMR8XxhU*a?1 z+-aWuTr44gBXAtpOFFI(?p4_q>i}mL_46qjS}QMyUol$BWd=Uuu{(<}O`~0$M!2iz z8;C@y+{2W6nNS%#6rJ}YoA2MnV-qW}ca2gaMr7NHRatIeXq|bX4e6VWcZB7aOAoWz7?hX?_{ihihIIzT> zDg-7sl{1DKb&8Azl$!JKD|*1$Sz&JqnD*irf)63I_glC~9=5?)aha)sksqVuKk0)Q z?i@l>o^wdEf@=m(#rA&-pEpMZT-%e-d{l&of}o zAj6^&G3o6c*qMflC%HdydIzIDg^JSM%iLLqbPa<32QZqqG&B}gL_e}J^8a%14949z zlI$MVt5b4}w_p=QA^g=Ww6Cce`s}f3!@R$$LrdcSr13Nu$q+Qgu8&Sl&)`l@;SzM{ zRWMX$i!!J~T&8yJy4;Bm9po3jc_j|4XZc4zC2G2%gh&L<3=$I}) zj_AimnP^g3RxpguQ|S<`9FV7?6PbFS^%oAGK0%m|LHpnw-?z^*zqckt<2e3UT>I+M zqd41|U_a`$yqBk4aUzzX{P)(Q-$l2@1W-StwU|3gnftW8l$Gh)vnk+uHCz+_=9N(9 z_;OTkt&3au{{REy`gVsWUkU>_y3S);9Vn?e10NQL(<&rO;y`Wv^A`@ZY{E^khWgrAd1-*FN~w5X$Z`v&|b>2GX*)?8R z`&}vW!c}QZaVQ)E61rgMd}uNS#E#cYz$md)fY229H?ltJTfRPjRFEx+AX2c|Qf3i# z1C~IdVfA)}2VGRPuh}B05dj)0BH-{}8~9C?W19=-^n-*T*#}eBi6RJjeH)Vi-&=5H z&Z`O!vzZpF=%s!p0FB?0rRPk2a-i% z$NXN8%h?N)#b{me@D2jRR&+IeEAFccTWdQ)l%EN?@-%rX(Est?_iutCJd*ZnsxNhN;K}2TcSg^CEB4YhcZv!d|De^WV)yI$W+-!~BS~q7~ zJ$Yu@;v7xdk`qaFrz63ZFTwDV2)+rEP%NjYp$ywILU2QzFTD3<=Rm5T??fRMbRpsdEHLAQ@oXUSLCatbHulFDF2S&Y> zsKhwFE(<7UuCb0a9)%{-DY4g%NTiQ&3%Lx;>skjmd3N5WaTYa!I!1I*?T$~OhTkz0 z%SDEeW4twhiSRm4Jv)+#HDHhm-!)#)&;O0{CzjEyA87{8{g#R5eU$f@K{Ua1ee%Na zh~Oa2;IT}PuRXSCA%xKT51ipN@;7!`a8vVNE$HxDk#uyn4m-E-DrB`Um+d@#THnTp zWqW$^m9lCCmnPHquWg@sBU(uA9*0!vFY<6NijRq-CV6Ae)+B9#1=i|} z^9%SX5gPYUoKgqKhS3(dPJ@wb??}rVRdHBfXA^9`b0PT)K^RvF%wu0m^9q)H_g?D-7r7i60QEC2mwy>DPzdgIJPli%nXY(-kT7 zCOfq^&w9M}C%4^KDi0r<#E zXaaV-LBvGZeYotxUMROYsk;m_;#YA1QILe?;tQ@0iTZg9BM3LaPnF14$MVZf6 z7eLbGwzd0y6Z}*_Q1Nh>zyqx$_z`p0>d(B^aU+`c@e%f==L;_QbBbxw?_@t3vWI6s zc`I9E!@_V#QPk2*|3T#m$Y!PuGw$t4W*mD^k)o35+!3a5op9UavJ-fQ#kP5;J5d|8 z0-jYm1ii)JbUDZ9fhBO|*4TnEwzxSga~}sPO-U-22+!l2E^KgGsa?8tOIvExNQwoL zi5(+1YX2AVV=&doV8zvy?PdjnIiY|D+bj@w_xxDJ?x*LUbjg(-ji$j=54C$y1ZUb$UBf3IFZMpS zYF0EZ>WdpL*I*ss^J_yQD>`+5FcnULcSVgH3Jib8-!q8%Jb$rHYz3^Wx%|OEHCP#9 zPi6Dh%RGYF6z__TQJO+~Xzb;t^TKXQgp%~m$kY4{7gY-zeJsw)bc$^1SY4o>Ls1C_ zKYm!_yMiqL5^!$?@$@A`rJXK}gb@nGdrZevfg1I&|zApVhVdjJ#~TUb@1g|46o`Us34nya#G+TZ@T1KuA~7 zgmJxH+edC-50Fy)_hdh?ZQ|A1s)~>gjsqz{A*2WH+Bxmn_CUx=c%d7%KtZU^jCIs@ zgprR|OY2BTE>0Y?K!FI^{tpmq@!!Ay0S?1xar-W*Is1Z8dr`W=6IqqRWg4f>Djnj> z@HGKq<3cvqQiZ)5Raovml}p?4MA%P*6Wp(^{mubS9$re%s`vGOJQ1;{Ai^MqVK-`n zR944_WWf# zouq#2a=wrd+fKSwP)x7H`=(>+57hMnrjQ)=X1k^m^EDqvM&J1hOo+@EzZ^T`=w z+6O{Iv3fH0G&*$hkL@__<@>Us=rtSjPXXIrNozwOWYi^9o2HLru09|3fZ-6RaW%{y zsvP}eZ}lXC(Uvz#3$TI9V2O|ADjU##!u6|gV~pO{IB!P-`c_Q(gypcjn8i&qJcH&0 zLUS`x$$t*}Gaydo-xR}5AJvQ~S-w_}W3vtkelRT|ErV^^Vj7Fd(G$7tX>yHW-$b^0 z6vhSsE^Ma)2ZBMEr(MbdB3fn2qN@8)Q<+L?=RG)phv&L)PW&!z&33%PohzB&Px zP-|B#?=1M2{!H-k7DePOohNp94Z8Dlyo1kE!H+T?n~_|Pc21pm3VmAXiywk9aD$=wR$7} z0tsf*52aKY2+i29QVxma*2z_7c-NJp+kufZQvElMpo>kwhV-+-dV6$qYB^)Z&25|i z0TQt5r@e@qA-lD`3b6q?vYqcjZHv^+(U}fyR$j2xccMX+kxN%G%dA{ z7OxdB_@#I1)<3J%G)5)J=7mJJa<Tf{!G`7!MLQpxVrdViOm7iOimg-7V}ICZ+mSPDoA z!V9x??FPd*K7EC-Byk)9JO^%V9?AY3{eJx%>rgT|UVMR$&!nH&!(}k@I|ym=_GCAAi>cR0wOF(&zhn2j}*J&4-Vml~(Ki+faWWme6*;fKUkW*8@^7}vf9fq}F?f9SpaexF`u5;#$X z70BENFfp`7>@GmT7B5uZsko=9Pp)Bhs%&GG%d5NlGk6*jRAU4rI96l}!|gwsOp#Lz zyMd1INXyB9jS*doD@;@kqU2Hf5#2`CTZ+Tceo5D-nrv5Q^wWlZFmdXv@hlFpTQ#GytWR1B>LAd7#kG^91Ps|2j{egb{ zzDhz-Rc0_vS6o`B4Su)-YqieRO=Ll=(Os1_&)S;r8iwB&jY;rnUk@iJ!QOUA8%Fw- zKAU-z5ActKY3By&9z081*V|jSHBRZ;3FUT=>pM*!zpOud34kXlzFOiM@zwvc-gM`E zvok=K_--Jh&1irJ*F0xMI(mY~d}7=6Oe^o3D>Z+P&t20=n1{56=rH~daE*!{plQ5B zZ392K43bO*lrOoc&zyxkoyq#!`CsRT7|TB@iW@`dFkk;(R2(@LuXp@(mAH)lAVi97 zr9KekTdyZbk0cbmKwiHq`4Yt?Je^H;(beG&(CM21{$7IyYV`zt5ViLnDE7NJhj|KD;ZVTd}6Tn?LpFpgewu5{?mB%={0z zQk{~ICc-W}S>dfQX^$2+@r!F|<#dzBvI~wqErI$2KKT?IXO`Q%6kBf$Z_s&RKL%kK zx_Bf=P=7)+=v?4M7I7A6g5qvro zKyZKrc%F5}8elRmFZiaB6IC|k^zlQtjj(K9QtKB?UiDU*3|iEL^)A*f-m-ew`+kjv zVWY%v({8x!dt1gYuY(vvZdi&r=vubUai!dox-fB;aB$%uJBo-9@5Ja|PQDfvh!;ZX zj9=|w4n>A9i=#`-n*WZH1ZbRMo*AK*2u-+H}}Z&deXK(E7i;NA6)& z6*oL)zgt5mlZiqgcK~fEXimsfy5qi$!}^_ul1`tuzsOkmii~Oo+SA6!9sSoMzeIra6TgiCz$Wp%C6H0+E<;v2z_7w>bMidq`y%cV4K(@?O z2qeG{W&I|N<$ZgfZry2}dIUeNyjxPjPN;hKl^b7`_%q3b1k!_~7n)Fc=mY6(NoZa) zw7<~4ckU1D*2!JM=|M}%6RbnV8Fr%4vSEzjYe6x{-Xsji)l zS_PAO?u3Fiz{om4q-^-63yGI6f-1iy#NmZ>_>EavTU(dPt?jE))JVt+Kltm%24B1p zADURJ6clWY36Vv+`P39LtWR->!q|o2VI&$MV>Yh}$Yfo0Wmm%Uo!+iM!9-@-9+R{W z1nb7BVH%ZXuzV!qzNF}z@8-$S0S?FVXh#NoWk;Z9NHEofHd{;lRmMHqFp_#y7*eA_ zul`GYex)V{;y;V6BI!(SkB_&X614Mk!@{WBF(jW}Lso|Z4G(*z3-4fD0Xm zA0)R1t~xVC*_S#WIbv(XkL7`}jr_)YHgik9jKcH00TRywPFd3aO-pY*BqZ_FW@owY z9NwhN)S)g=FHo*HLInJXK6L^aQ_VRq&w=Sqt&bGmb^xrxBJ$;TJR`yC-e}aJm z=N9?PFNY9<2)AMOb}^qZCJ7_YvHf`t44wIaA>t)AQ|y74p91%^DJ6_iRo{HHJWuS3a%LuFlT%8#&2{AJ9Z?3(+G z!|cKkJHVx@431yx#j%B{T&{X1n5{!c@6C|n9x#{IU&=E?|B$I~)cqSE_8e*57K$a% zrGvid2$V^xeKq~tNi+On@$we>sAiR;Z#n3X=K1LQC!gjT3qy+Cy11T>Rl)C%grR{- z=R@mOtMSSJITG4Bs3q3C0sdPD<_N30hyChY&x+nNu1IGycYBx+&|GXrTfo`_LZ-%! z=wlC6Msdz+wy~IScw6Y+k0V6U0+sQct1pIB4?m%Wi-n*`Vj-t&SF>#Ox5HrKlh z!x$3)prUjBrxh5dQAbx_(4FRzB&XR{2_F7qJ{cnCv@l)X{ZQY3IIo04p}$mm9X%~} zuEmZK|EETuVeyPpd(w-dzBgA&o*NNH*qFR1fZF~8n*4HEr7+FAgeLi$=!_SkNw&*@ zbQ(%b;2E$3wf+pd{b=7O?)ntH!fF_@sdK=5S4>J*oW@pTUsch=TOT1x@3EU6G^o;` zwy{B!^#TZb)2=q;)thXkDX2d7kfmj}aFCdxzu#!#Q*bPoZz-!(s>gIF0$frkcIVK* ze>H8YMW$J2CDP43$Uv?vP;S1xXVUpQQ{us~TmL8#psw*U;Y6NU1n;o8f->C62;223az^;`VsQoNi zer3^t$Y9D;HVSmoOikNAa89H<5Xc?GI{7~ zh`pZmab<_VPwzZfpjP>{BL+v$kQ znO|ml#-fP_G3s(}66K#KbbV`Npw#~PlcUOgg$bcJH%OZhpC^e8q?`?VIu#?2eEB~B zFL4Q_1%NUhg?6yJnfTx6-Ti=TOP5T!{0p5xM&HhJ;nn%M*XX1QdlN94ihO!$Dc%5{ zt+Dv{{J*t4R8ssSb{pAsTyAujf}YC9xMf2f%YHbuE@W({Uzhd`Bu$s-%GEn#l*>U( zdS-ZAQrom%$}YQTUrO)Uu^8csN@e8Z1Et$JRzzMcw)W%Tgt{<7FN|Y;$RuW5^k zM7kd+EaR#n^(tEWVM6J+W=P}Q3|P)diTsw$m^V^W`cY!q4@uHXCwuNUqJ}g&>oIgc z@|vti0O1XX5?klSJhah8elIB?SLv;RDZV<=f9c1f{JvO=D!UQKK+j}O5}r*aeIF$) z9h2v1D7J0RFbL!AYM8?cu>q&)d3wmv?oSzJtRonPYvOOrjCne+H_)6iD#w3jdEoFj z65s+$RPyC!^J5!2d^>-?Vpcs&D{2c+)xh}gESn%NX84nMD4kcjb&z3ro^KCweJw5K zeivty$2PzndBgx2x*gDh@%~OabWbEavx|c=DOFcDDblr(TgEbbrZ0v0F5_%ZGEG*a zLP3+PbK?<*@#@o?mLD;A-uNLL&^+*J{mbiV_Szm<3g(g%AztfyzwE80!Gt(WhTO5L zgldEowUVU{9=YoAz~TgI{iM#|e*nF#NSial7c~)+g}|C;>Kh!mq`;Lh)!`OsG}up& z1Ul|@(Au-CR(yuNZRvDTA)O&Qgmsn#lLry5`#OC7-9F7|z(aB?J33x8<2tad9g*Pk zlA)~cKuD_K^8#Qk{_CxMAGpG3yh`~{5OcnvmSV{N0Mp7Goi_J`qNNuZx`E7$XiwEh zJiSaf3Ye5m1!=gua*aGvrqJvr-3|;lrg^uls3sdMFwiB?4ULRgJr%6_ZuA&Zx^qXD=zxMaJKy>M+Cf^y zaea+#P$2(Yn7l`o@?;|GJo^na2^Q3NA`9nzXS$V{Y!q%3-@|oMBA;vnG79hmG)p6C z7YG!@NOYVfq{-)vx)P>AA@UxAL0gqd^B|p(Qg!VgkwcJ$Yax%*?=lQkJ-p0`d^=|Y z<+U&8pBMaC*TG1U)M1>wXBL?(kx8W~89j*@sQo<`yDxnxTa+9STE1>fZdA4fk5NXv zo2=lQ6YZ=GBtXNxt+PUFaf_Eu_k+%u@0jQ-^b-F57ITb$g}L|o3ETkObaLSdNvrRT zGfr;TF>mlhx9`5g6Hqi=`9DZkO63v$-P=-?ZZ{;@J08nGE035<)uGWM!%i-I!$iCF z7VaxmluUfNGDp3%#5 zRoR!3+L~C4YfnL~!Gqw2A_P%2$nd`uN1P!_<93-!+!3<6*6H%VD*D^f=!za!Z396} zih|V@uJ#BloJ@Ml*;4g5Jd7%569B)TR}AkPWcId@>c zf2#U}HU;_=3x1i};l5#j@wmYw07bsB%}{Q{`W$%!Ql_rUyyWPoMyUn=px$`7j%d5F zcEpp1fn#16^}BsUfq32y+->KA-h_pU+J4FQV;dQ4%?M{Abvq;Bc{cd3fpRKztZ48xvAge}d zc6vXqIh0YnJ#;UaIXOq@MfP9btIq5e!jh+vrwC&Oa*}Sd@`d;*>-|ZCsfT2u_H{O> zhOqlHWmo!U^W>t$%3hr|#RT#7scv%TXs~nT@sTob!HlV1qvhA5^1S|MzP)9)P4ynsb#^iai^Ww~0iNm0U;-yZ#f(|N*VfS)oqgUJ}${L;(GJIwC z3@20Rlv$an6L&0^{}CvzrkE+Jbl_)RIpev&+eN(&h47CC!)|ZkdL1f9T3Q(eWR3vG~dam3dUUB%C6 z8-p!2R3?i;fr)^8H7cO<)$Q}R4((I|eRx`PrGthKa&ePaR;Y;GtX;Zn_`18YDQEv> z4+^NiQF*yipGo$)aR&r5Z|3&yfGff!W3=rCRRX}kj9)1s3ka=Dm-GLTa5w64HNGG9 zY=lR_V*!+(y}QIbY)^2Z`_-bO2fSP_^Ss=TFf%UcH3V$d{y_k=hF zW=8W{Fka&sfc0xfo9*$Ls9=_C#v-FJjcTA_U_Xsle2vAxVtn;#hRG{qy78=ity0VF z2is}#cx&e5LiaVg13sp9k9;LwsN5eF_ffAu>8uV?OxM$$VsS0wQX zqjN$c`8DX+CZMil1q_KFPdP4x(K3!e`(d^?#i{coGx>;6uFboloI(-dwLunG-U|GJ zZzB3_!3#u~rgSaZn4?k*BiE&NbRHU!7(cn|zeK^?(2)K8*hwn`Oe{*>wu z5IQX2dg(=GfGXkhpO!O~9%_6n;586D;KF+%($W+^WS)Rx@#ateb4qQvD?@jl#IWn3 zHiZ%HRR2=DP`ITRI~BmX9rtvA>x)~O#~gPqW>tG)lz6O$FRM$hyjmWkD7`J8p4fB6 z0NoYjk^XZ$F^qYb)euUf2gv&(U4RRfKXg$AS=>#O@G3&B0jt7-W+HhE2zQlFXnUn- z_&F+kP>anT{zgvp293ML{Dry}X}n4bS_!sxO<-2FCI9vAI9-+Z`WHU%^56Ts4l;np z9+O|vBXZ7hLT8{G7VI$IAsKI3vwjO$(fw`l8s8r^`{sv`w7o;8wYI8k!gOctFvJNM zlx?9?PRHwFEGuoh`UlIv;|t6CeWS2|+Fbr#Jy|sW&u)bxqwsOiAxoMDpx%0Sk=;SZ z6#a6g(~5RUaWSh>KUnr?feQFlnTqbTk$NrZa~-YDtJ2#?FZb~-?y8KzUX1%~t)yVC zjn|Aq68Zk-%d5>*hAph_(7w>1Dw`=5^gks&^^i4$!#bNt!A&Ed`=u((a{Wf~|B z+TkfE8K3Pkepe{5uIrFNKqi$iRQd5brZX%IQs1znu2i8YW*rtq*LSF#H8IilyQ&5L zkL*A^=8Z1W7vO5zRuzw6C4b2r8tL{vI`sP<%$D&C7;1kb-9g!?)Qnx3P#P`-9c-G&MY4pn@2Zu~% zPsmi07eRj$rJXJgu+AjD@m!_do#fK1UbqfUe5EJ(^3kEfUZA06o6$13HZI%ok(Bc+ z*_6h@#i(KxFW`DvV1z@zbXX70`IzpfP ze($Q+Oe$u@e`*G@dtkknmvEhdSOr{~bmniq*xqC~D+p1-cC)Mf@JhLknfV4|dpF=* zoR6K__;t*yXD5e3wqUN}W$3eF#4!l&GW*EFZiriz#aw8$d0`aTFoy6*5l3$y^V$JO z?Ftqb$4}Z>?mUIH#(yAOv|iL$YPpP6_yvg5MO9y=L_dM`5I;=BGe3+*wI0c}+UOg- zA|uWu0QD7rib$I(d%jvpuU`#@pA-gyjt5l%HVR5o$T&C4SaUJzeKsQcfUUOnmje;6 z;<6cnZ5Q3ARcj0&x)x!>h`P!~F@lryYX!vnb%Vq>6qc~8V&Q7eY2>wG~IMV9lPxUdYumGlizM3Gwp#EH3-Ma)HvA( zP31mUM<5&{##X9hj@$C0>6i|GA+NI3?k5FQX}m z={aDQ@oaSbi5eQcjlmx?KC?gwg17-cJ(9Cp7$l@iwmE(T^RobCX zg&t}I{rT{+(Y}&Q=^)y#T*b*Rf7Z&duDkHZry64A4QKx|XRQEnGLW*c#`mu8tB?W@ z-5*G*&oZ^4*(8ia}4WLzz(JArL%Dyu{nv%-wWd{N#v3HlxEPj ze;t`s<>bMbFPtUb-uf`)(5CW2CU$c>y(4Vd;75e)&Ix$q`BPbEa9XH~2MJjf>sZnb zXJF{JH6ZVc(s1QP_PpK(hWh8{C*76wb@(pq!8R-Ldf)1O?2(G-s=9)k@~4O^H#$xOup|F;TEGIApGK(=#+R8 z1coyqY24SlQ7UFVNPkV`qP)9a;Pu|>S35#srehuoaR*=$Cl37d%A7IPD%~u}D#=9V zj#=agEBH~eyOF24NUn$Kc(U5}SJU6YFNDa3uQi@4Uav^H>9*y+_9c_X(XNQvq(Gas z7o;r5Yg-eHYNUR&PYrh?d{RoZGP`W_X7?B+*o}8%3M7NY+1=nPy*GW( z>+Fg_UN2Ah809})OnuPq`=8N z(%AS@bk$>er;;NY)XLjssW-iwV0jO0i`|mwe<*3mBiH=xH>NRws-)o-7_Ao8Z8?8)EVqLyE^*aSp`b3!_ODhdIY$JBFa8_P+MY53j0maw zPcCgyR@ZoA%I$VcT&yx9tG;M^XE9EGD-iwUtVAi!G*YaP@G6%dqZH{@bM4zxMp!%T z#hxB;qf)I#hwF2M3yQ^q(mrpHy>G1?J#qxI`cv2YjN&|*pzrA*A0kC%%p=xrn=pDJ zvU(oIp|c0B;%zb-*4S1ml^$mNL94UB7tL|Lfl}JSxy`a5wC{0|c6}6GK7qq>P`G4s zX-~!UG>x&MC>%uABUKUd%3j2*HcOAn}~DbvCF6=vl(g3!gl)T|g?O`B+XtpBGW~OIH|Xdb)=S`4R^Y7W8);AA zuC+BpPju&5cYjHdVdLP1e#kRx2x|9;iQX3vY#59kkKt0ykxVWfJU4X6@pm;<31ra&FyET~F-UQee@f;>g4o*qX@R=CMI}(Vy2o25F_{@Exi{+GCJs9N z#Bn?I*`(_q4sb4$K*>stlXa=l*9bFNHk1>yRZin8cL=K3V+S9 zl8p@IVrddl$dT|-)0OVQL?OvcBo#5Cl?LVgg&h-es0(dp%zVUQ2A=u8PICGtxE4Rc zD)MzZa#<_yO&k-$-U$mssXbiD?jRd18Y|Q&~ieyBEVz)u&gBnJl}L)Q&SW9b8bvp`x;ro$K1+$ zbT}>oORq#a%lA!*$eB6O<;_T<}HaRQ0ss&i+M(b)Z zEp>GeZD^EW2FjJxKrbmgl*?LzDyR}qbn00ZiiCX*wYD+VwpEBu!AqQlf zrtI)0D9i%KLmthXy5HJnM}EEmaguT6U??PP$U^H0_g`E=yg&#EmVx57tEqO$DRD_p zAcvGdc?O5^FIJ7KZW*1lCwO}9A=7OpHDj0jwsk%pQ&_Eo#5;rA6$<$fmlP}&*!1$4 z@kaNil%irYFL%_Jp87yqL`}q;D4ud0z#OsgJWl zZf9%AD4L_l0MBD4)kDvjvu3j(h7WftYRu)y4)m6}M91`vFUcPz9~t&I))G>vB0vQJx;j*fZ0rYBCCK-cLL**( z#dN_}cyXsN;9SYM1*SaPGfnyxsx^RNPl6YC9B))Rt4Lp zCOx$B${4(px{lukZwZ11Nje{LW>(N_3Ery?R)*| zGq*s&!t@}P;GltHI`5%6$MKsIaNt`UI`?w-?TLdLJg*`=HnOP=xhnJi&XQ((Gn^;B zq+t21w=g1D0^`i&>yB-E$24lZi>-5+Z5_Y+H|vhY$9T8^P_z72s$LtZMoQ%AG~aKQ zZ|9O)pGo)XsV>=v;zwZrVolP}-=T`UFh$H_riCS^GCnE6G6-B}aBtDPap1hz=Y}ec ziWGmiyUMe4bsC$F4VwwI0eeuq4b@FP^bRX;<5pso#^Va5e%T-6ACCEERi&UT=b4K4S&(NiHd`HObL8J? z1+RR)G{`5(p)|_F_3K5N*OKW&bXLrI?*PlQfw7+8)uNmq$Mj5j%=EVvhN6=l?i%$n zv`jpE_9~~zEyJDpI`E%`^%p3p{3z6J@oSa@9^d|VOhYwP-F4k~?2}lfl!04KFL92r zqNqN*P5-JH*8U5rMRUnmYFi;xYXn#P6x_l2eMz((4`Vdd(XSMV0zF4_DoZy6QEY^a zJkSY~PQAB9kTihd!XtpDtov0sf6-GB?e8A&1+N7Ke#^jMPZ3SvdzFQ&MPn9hbR59E z)7*81<=&TPl>ss_#{$Yd_5ikoD9V2>Dp6d8BQRBMMwC$ohnGvre_1#hXV`yXA|7mZ z!j~Ki(F_|Y_^npGxS`GsgDwTg_Qw=ug7do?aoN)M$6ISpoS}6D{A%VHPLM?ZvnJ0{{6*dYous7&4Sw`LTx?9~&!b83*wF#8qHT31*aZy7rW1 zB?be$2MiDC%VL@iPlPkPTK_5E_un+%Tn_*DI|WJd4EGsX7#=p`n$A*KXgdW24cDuG zNg#|zJ>GO$jKd)r#Q^e!5pvB@}9F;eLk<>OWk_&rL&MxCHJ;E-my8 zK5hSvaTr#@>m#t=6d5O!xki*!)mR>^^U!&X0@H4k|N z?+9_AH*`?9%zN(a>9KQiM-5>!X=&-Fm9Uv(Gu#q#BTfY;2Z;C6eOaHQj9lLD@jh0_ zXW#Vhh-5E}JKLn9vLQtq54_-C^$Yseg+C6>C$YU2R>ypAQ2rEW82isqFSK<8vM;nz z@#9A7^CC-Yh%!bYr#Fv~1fLY}8}gQwqk+6vJ}_#GrUCruWbiE$_WFb;(_bdr zZE1eV@NpCiPAA)Zk;I|wwz}2XoaiTUac9iif+f6n0&djwV?vE_Ex`iDA1)9Ku?Vpz ziwtk&SsBJG_C;z1+Q`#PYczmpEaQ9W2In%1^n8kbhvek_S8krrwOjMtp5F7K-@5}_-9$5^VR9FA0Slgrv`COqYbcCLlZ`~iJYnk8M+&V zl>VSQ6~#Pnw{YdtFA7pdNi@fhIDENEW6!RTTkO~;XxpmVMP4xM++QI~TDc%HIDE#<yAb-bW&dWKVvzDN?2R(HiV_GKcsh$MWa|LPhTpxg>GPbBr)w6BnW(xwng8B{ z)xx!dv#QdsWKAJtM)6z=cV#X9vVLb@Qw_lSS7A5ut{awUd6B%s7{DgVL=1OAGe6;by5~v0Y zk7JOcuMo!JseE$#2?DP9fvmJY8?Z$OmILE!p~9_mID>y+`4PC3R^!yw;Brmvw`4LW zh&cGqFmhe=_^Fai5KZIt)T(Hzn-f8JOhv-xeFa(tMzIgQ>C~k{#%EtW<~9DHN)P0V`DRuzpYS#d49ZffdN)Qqut z7cW3xDJWLvdqR|v$j>q&-}ihP2m8}YGdl7|@L4cTx6VU?klZx-SYS9`aodd{1#mz? zr!d>&3j~xI=WR&lXNrNMdtetj>|a5h)A&c4 zRkBoBt*qXSsEyM$bN3Rf7k1cf{%`CxjOQ-9SU$g zEsGL0dHUBdXtaD(ld*N3w>s4_;-!2gJ*Q>0UK>|?tv}9Vh$=U(xh_ zPH$k1E}*{cWsd}#3K+)-KzP&mr?d>>%1mq0gmBZ+tB2mx-CK89a#lubd1>m?qXJHa zf*j^^@$a~a$VQ#bBb9Xj;J=5?;T3_%@B(6ur+spjgk5Qwep&`Uk9|DF%KBMxhBc5m zep{3GIKy5{+VIjI%3~dZYo7J6vbBq#UBW_VClMCS9umXJ4gu*FAUJFutf-C-xDmp> zN1j;N*R?KfWAA70V{vWe!E_jnr4-f3eoS7GXYAM(Lvmo;I&~swlBE9>2;3#S<@%T` zZ3VIV%lH}3Icriwh^WF%o}4Hcy~}JptT#7#kMxajq1-UIpZS_oz|!zqI%eh?d1y$- z5b)Y8`Pfg}6=Xi*T+k;b-VHppuHgrdH|dMy@$h}2sDJu$gqXm&8V9^nd8Y8pH6C-d z6`ZY%Y%pHN&@i%!nc#lBaTQz89#+)x||c2Z#rWJi`SuiKdD_QD@HKeFhOw@B#qfe>h( zUT2)H-q+}DN;$`BDbC;jMgd@mjX#d?V$~7~hZn;E2;1i8os73QUOV-}bhv5c}W|B;%~*Rm@-gjWbF+*)Ujt#;Ay zToi^%k(LiZ4E-hSMc7bbtOFInNZtuWEvQV=NV}ieK~VfM5o~qB@qVkLRB3y#jmEKy zsfDlNCD!F50^}p&sDxV6o6;OLk;vbXcYo(C?BG^w3Su~Ed&f{^1^yv-o*5dR!&|t{ zVlL@RZsNJ^mQCo&sSB8C1JNESwB9QnF%ogQ#;irF-G1}sm6#Zh&bF^CULyol)9=(| z%)``hhcLx)u@4dsazU{sjfi=rRkvEMci2Z5tc$zpT@WOxuMRbOA{QW20-tDPVVp@1 zy>5}1yD*VytDjRBt{uimXL8`8Dr~N}em!~ie}G(_TCdnozmt?iEHkthxSxx$Mc)sd zQmS;|8<7W0>9oO99@*2f7X$2LEQZrsqh zrA>M5cHkmUZ~FnfsBtOYlGyF{mGBr5Wjnidhmc#Oxtb@GyfKwFIay~iUP>P(9AWWG z&gyc(CUI^weaQ>}3QGWHEiE}@L=K}{3URBRYFy3+opCJjsR)-tzqt2Hij{^P0O1Jv zf#y9kzB!d$O^x+nz;MOB?2KrzxZxv`Morh)2UOLi!KWOOTzIOi)b?cq5jiG_{Qahb z2&<>m=QXLCoT~SCd?~0KS0VZ%t5u_KTwY?xq9kie z-LEXzQxa|+37aSt0W^qjR$L-G6o=MS79I6M&85@!d@T6C>wkQJ7myO(XPz5X5*?BW zEweOaxvM%PfNY7K;aS_a&JZ0|`BqQv(EC8BCRg2gVB|17pqQQ&S3Rl8ThXn#Zc8}z>m=i{x#9!)!JxN~P16@pTdbgX|&dPP~f{F_|3 zaj|NBT0F$V_K%CG_NA~OcF;JoA>vibh{kjzrSlswNDzD-ohZi@G0(4vY>|cDHXvB9 z6w52{x)^HKl6t9YF{jHv?E~$~X8ObuvD$8hs0JHPU6aBaz0(u3*;&%Sdk>9lIdfoa zL->XSs}gjgRUBi9+}Vehr# z*mn1MuaAQpccPXMFF^d}(4Lr^(Qm`N>%PIm$yU!4avLQDA^#YdTewhSe9`X)yH|Wy zIp>?A>u$ha515`r72xD_G-aL5E3of8EhfSWjbhr`(_s{n>{0TVFu_ob6UvZCC1^=N0tK~L+{(1078 zRx#$}zFg%wEJ)uNIdO`p?EF{sVupLyKFG zm}5R}H$tm1=cJnNpQxw~&#R~`oALF$9?ahd5xF_8LjQMD%=<|Zb|ERDir8*=94=WeZ zP%aTWDa>c;%VfZI^(wb){i>o+~pkBJj@m!7x$6*xwn za*^;*`CfZ1lySuypctuC}C|XOj8oQig_%?RoY!P5DF7)==L)&lK0(ht6!PsW-UUm6^F$` zpdbW(l@%|p{v!x_|Kc8zqy+pAJ^SnrUTOR0Gq=qA9H!kv#B^`#m}|WvA|%*y(Woor z!#neBwdf3r>}RS>@$W*RrvrO#t}=phfxn8=U3tmemOB%mnuU;#-F@%YZoG$aVmTMq za1@&f;xT21xGvi=fC2L`cp}fRCE4p#ZE;IeTlyg?H638js-^8F_mvTiI0i zU>aXwc8slpea=o8M5nSTA!AcGS%wW1c`@vznvrh~XrR*f>I0$q%M{T}XZqR$b%`EG z-=KuU>zMoQ#hOwMXDjQXL5iODmB-J_E$;EUsslP%d<=~kSM#a{eJ=O}(@UZ!3c0Ju zZ=J@)Cj88Kx{b)qOC8Z8{8)1P;H4omK(Z2cnc%eDNNcV)0;kdoE_JUinky zkr{0!M=hvV!P6Vb#Eng=MkjWgz7`ZAE%$#jv_GZ(ngyvzaIwh?zT?3Hz9PFydX5kCbG^Ej<73r+p!?FZ-K6;fPB9u976m1Wa7y;x@(Hx|Ns@ zVSW3$d7tw`pJ0tO4y4acw_is7l$n2cd=EOb+9B7XZ$+@-j0C{l zbB6PR_YC5GJ?@)myZL^PMqgBVK~%`!uxfJBIKx(D+f56m{8gB+=|So^#Tr`2?U3*>kFoPj2AM9!I(n$`#x;+*4YV#Wox5iS`4ml_2f zttZ6DSL(m@znR{xWMsUa;kKJ0#9uxyB~4be8b^m-JEJK*djb$etNN4S$LI_UUK9;( zCl+12E)+8kBf7!z`G4juopj2#=O<*c`4+E(wMm+7s4U;nsyr%Xowu|RD{&4sD-g7D z_~?(V&&AeT9Fp5a)#jl6k5!DRG!uU`JCrapPWx#$IbUL!=WG5A)LBa3ZXA$Fle63T zF$il0cvC5K=v+Q9;u9iD#zcB?1Za&DTM@9jS7~kRVzk$CLmFVPgeWiZ6hG2X2O{)8{odMU~zkUFr?b zj|MgydmvAQp&jD8sx$~C!l%}XlH&LH20VHJUsOrCj=Fe}uvd%}n&@;1>ZOJfDdDG6 zFMiWkj6Vl9T=iFG?Mn9rV$G3(U zt%QDW?2<7mkwf$0TaF+{XqgbF!0{rQh8%_kC7mM;4=<78A3hMGb>-RS8VljRqo+kH zOdgN_ggebUFjsz%`x^Ble%m|fU7)DGxofl*gg&G~GIz+!pwWT!r?kpiZw8}x8nhMT=(z*APvcqNvBk}^gLTZ~PG+&*56pBMzE2YI z2X;j=rVC9}X3@2+3H}6wa0=n2L&?i+a%fJX{2mHoXv5LC*}R_CERB6#vFdcAIz7eS za+^*s0`4PklmT{DGU|QWVFNi?!dvmXZYF;~T(#I2)h;#P0$;0zlONdzz6LRHH9W8I z9#<47515>=TUCcVT0dADj$?DWq4Zt3;7325#1N-3g=j({634@RAx|!?6ExF4srD1` zt62EjklG#(yg66*$zPy^P+N4|LaK`$gc7Tkq?rSj75>{ASywkPS;ax2>W>_K8#bwj zN6Zh{&F*9y+hM(7w@QYM${OVch%Fs@GlHiWxfR(J>kq^Qyeq%KeM!*KKT(G9}s5fXG)9&Vb}`k13KVi z0<(=#25*SkUceVd39{wS5qRUT`iHRx?uVFH=)4zBWp$mwVo#P;9p{S={KBjdn_@<0 z#xQ)htDN@4#(ZHUHN9X)(EtQBC&cQo5wjpm@0i&U;#MJ-f6?|EX{~a;4UuV}pAS8*up=u;DO#9_bmy&wcV3lZ01dTK_ylX9(0?1o8~q(DXg#!Oh1vw&Dpn5#G{# zm1SH!OVjnHswLW724{NCxag5K`%i&-Ug(2Zq{X5}u;-r6CsJnMeY}marK9-8`ImD8!}-5!{Bk6ZpQv{vR{Rez)%nLIKD4ct zH(=bmg(nFEXf-2uD34Mo&GL*@{+k`xg?pmc!Gp9Y`tT@&~F z9Ly>rtr_raWPd(pUa&JYjS>-l_16J~%ARO+gr=#l5+RooJpL~)P{FnnrEDeYr4 z8CK4R{Cz#t7q4zMnXxW%pNMZt_C2=C6IV~??b?q1%!8QA()yVTySi`T>d5ju$cwC^ zdjVuq(OG_GT+6r0Mu<46I`FC||2Rkp!U4Pr_DNAwQ-Li3sF@0hd}(+82ast%DdUv6 zP zHU6ExRg+(~?DeBWqyO=Xg>A2lBp8Z6_lf&f?7m`1l$DnyR8f$8>s%`8DY|8g+tfVO zwgmNcIZI5utq04-qBvbU(b8WQ-b7})19i&wx+i_ILd8Pay#cph+6KsqCm#D6;5-8y zHEjHjB57o(AZ1?k|l{yc~d*s zw7_juXD#_YMLb=w(~QqKSPSZMu{z)i<({Bq5iqp_IeBZ&N>q)LUpMZFd@^^U7UI;m zJ#r<-TC9P3spo=0c=eIUZYE1BY>YpE#|X&&QY|{2I6LvXC`V2d*8TZ+zJ(@e)NO~^}f6+E}iuR>7U z7u$eI0&nQ4lq@+T`rA50|E#iw8K<#Z4lhJD89NLr7tzC?fv47@RR009cVAPxsU`^YIttv>6VB4`Th?9!{{HdCJH<|a!yviKbfN6`IDi*M^kF2w?7^dO(^&7w4A3FX$7^y(M zHe8AEb(+9-C%p<1-&$shKY@y#YFJoQ{nPgMOjz&Z^R*FIAzALe-j_bn`U;JkxaV+E zndxZ{f^7etuzJATg%_z(wYLc}7@K#Tvi6Xx6 z8mm;E8&DkAgysY-jY(pwXp-~(*QF}8^M zneK0A(b>AsPfxZcGJzSb@gDg#dUAz1QI=D(^E`d$@Ih@<3vyeVTr#lrXN4cluwWP~ zW{v(;sFSE`d9xCM^9$Uz{oP|D(TVf^I)M@lw-u743z7*KxeKLmuuwt{P*b`xGw;v@lgt_%?r5<8!4IA-q z;REPmHNC-*Pkc~%Yv*lgv)vshL4VCqHdOl3n2T$t6)+7PJ5ZcXGMSc=Z<)U=ShPN` zGcSvF9ItY(a_qU-pYwsu{$r+mkEcc7f?;+)7a%{V?Iq}7I@{n=A%%HW_xfJ<`*3SNa`TA3tG=ZX&J66mtR$I7 z(W>H_w(9u^2X66wFjyS4o9`$wV5p@=shx(l{Iv@-m%G9-a;Py^DdN*GKA_x{vEI%W z@#DLB&lWW4>rw%3U^&E7O&EJGZ56;lA9^R&wTe_~U}Xp7D{%mmd6f5x&Zfg`xbwnvsvbwf{JJT9^DvtR89yBzIQC6k{*1% z41D7NfJIX4(p4yyI65w}f(XoWJfvzXIQb@lJUS4=Uh;@hb(n(+cWR8@h;D1o7p;e0K03f}YN$!H=2;#;>}YC4e{H(Z=|At~ zR88AO6WId5{{fytt0%CDyy2LJC56xbp-+;RX+Gx-T6`o3ZlCMAV=<`z)Cf57=eclu z;VXR<-@9=72Azkw2N*9Gy~DS!2I9ZO)Jv8|_Nk$9b&W&6c_mlo;ktV<_B&=q0q0Kv zkRI*QsJD|C#T)hSdUK)zo{Gk4;!jLv7%?FmHw>LEYd+TTzNygpc(2U&S*t&~tXUuZ z+&Jr=V)pc=Ruzl^9KV(&!ot^5ozCSEMQ|4ykFd^bE!FO4+4FUBu`E}i+yjQ_;iM!WX zT_JuVMbmzUnah2C6s4}t>!beNE=N{qE9E}H&7JeP0VvObpvqb!86SAeG_bekWeO#k zwSY2szgt$Q-B4984rGOlF>apLLC^=TcRUl#`End00wZSwvVH*fb*HxS%+3g^*$?TT z!rQ8tMsHY<;|eo(M4VHFqqSD0miK|Vb!5>qdUc9L0tj!9+ulm|V#M|1f-3y5u1zhA zGE<^_yfxG(!r0lCUDHF!H4UBstZ)wtWT%c8xbpeMjjglgu1yn&U%8h3$l`;pM1kb` zpO-7QO%z%H=je3E9~*SKJ6b2nVxo@MQgYWWML0{SoF^jJK7K`&{Az4X*^J2G@Ll%d z^L%yYCC}>ZZCB`5J6+HAZ`Q((q718IE}oHj3o@3h6O+dq$7wU%kUt8{c8zsjDBKYn zv!Vt}9~(|;68VTL5B@Tzs^562JHNLQXuL6c{83d@??v{6mR^gn^fUH=5Nc(7yxj|L zbqebHTr&F#XV~eCkU3?tV51G42mgXVh67Ep`j5mxeE10`T7CeEU*YUX&L*OA4(Vmx z9b|^gCE%Z1G;y%Ek>`25M#p=!1cY1dNpVn{nY=yan-PPSP{CkDx$A5lju*8+Y5tJ{ z+ab1Dwqp82>zzrU@r5VV=u~yWZZ5=G^yyOK#Y&{rb{chtX=hv_ARc9@T7aLk)ZLCZ zf&VQ*$4y9M@gJqy!qX++Vn)qS&U)52;gz3tw30TXewOK)}hY> z(UtZ_foC}+wzx{r;VQ|%L1LkP&C7(W_RWYw>z_Z-#Nz<|{vP5M42R7(RdZsS#r)dbt=QXn746%q---WReCj|%?KL=Kgt zrmmXE-s^wjwF!o-S~Osj`3*4WFB6%R)iDQYMxBm{c^J`NywaI(-}NaC)mzyJ0}h_{ z6|b{#|L3UW*4q7D{J<}}-y^j8Li!~1Bk~SiOlIB4?v;F8Z6(5wZ#7S5s>>4_O}#&~ z(HmD5*8NRPwD;W}Pun&17F_Gz$$n=u>#wVziSpLj<|nOA)?DoBs5+IntGeug$vfRo zK*YjBp?}LV>tMEzhJCGXPJh~PQwHh@kn|*@`~)hRepdYLewQq?5Ez7ucTcY-;FVcw z#6;$Ea-ZPOgFP4(Le=LiM*eQ{ zjF?=jTILyW!}ubcD9eE@e4~4273!lz=sUN;q5oWHX6pvkgm3!m%`&Mw+5r^~kyl%z zmXA63i(FGzAJE?QzR|!dSj)Me?IJhCKb(@CzVg+jHF-~G)c6Q6QL<#*IbWU6)|q#F z9vH{VZ=?Jx*Emiz`P!ol$d3*7ceNmE35#ST{%Db#b-xw8n(r@IkkD<62{}wgDFsT| zPpP-u&pemRZ~X2W#VnNf_O57J!sD`1uuAw*es%L z`s3-7$Ew{|dLNCAs9OhLvm(6+K+@_(do7TYx|v)do4Y1bK0chVF&CplDTC+Xjc47F z?<*p_@&K=u#Y{F3VrCR;S>i@u-sL9xo25rP8gIYI0juXXdPM+@M&i%nW3}%G+gD$Z z`^@Ci6I6{1Yr4gH*YHgH2B!8D;l?}(`oJsko*VHq*#8w5(EUsgOHG%?=T9coMBz{~ zhO?DnQUX~VH>5?5e5z|no!vB!k+0@mRtOpVcMtMXQ-RpT9t;|k44isX5nnrVasOz- z?+Kd^_)+U8)}w6xI6Gi&a!H7n2kKxCZ!fBGpj7m8bK|AYf21AD9#8kg|HItzS_8)3 zsiIaYn^uRahf8xI=YiB0p(XUKi_4tIck??SgKs>l%^wKx_Z%WGpG5MR0Qi?5`8GOv zh6R_}E9~m{{Pq&HwBH)!s7Zc6zFc!P_bGA5ad2 zEj^JRajG3EfuSXv^q-O66f5sx`0lSU%L{ho-zN&a;OnErg&bJhZi*+&HEKm1=pcN< zYcS@G;p{IPN__xQRRVj+>k360Jb}S@cz&}=@aAe14i|kh%ynNKckiW&xLNy!(?Vfk* zG<1|iUe*p0_JjS-1+kFtC|<_<+UI|8`G9v;1qE*&XkUF#*tylzf#)&p9rV$rwdI8+smXm7N$;Dmx5sHb#I1YRBw+UnWPpCND3f>oI-etr9 z%#w{2^3_pKC~}M%eciW%d`W+VMa396V@Q~>Iy!J7Kq{+7n7Qpm+3DwpaTRlX43V<^yV8Gy`X1$9gdZb z6uluMS%;=9+NFE+!DcCue&Zg8*6xBRW~e;@TgUf`P?Bh`h$)a58Iw7D7Ii#F zzH1Mbnk^`-k>BHFAxoIF*eCRpNg8g9&KUcq)V9pUC6&tsqtX|;qX17&(wNxaa*NT| zEz(JMf8`T_kbjhz?<_L3iI=84a3phOo7o<=DzC%feFENFx>jykZ%;`m%Q-9h7VBB6 z)I%myA%1!JBDn%yINnQN zx``c$D*l4EgV%NjXOeA=Ad=4uvML3gL8hIDZhLDl1cZ35Z5CZL0|zOu)D-yJtLdk4 zPZSHXOgir=+YN3r#g-PU6$NM)i{#_f&~qHX1zUZOdBgfSMNc!Z+cT1QmTq*|T|hyS zW4br@lB^;BTGQkDK?(2qc-?Ioq!H6~x3x%YLzGw)nI_qfkG+$G%gCt?lQNdt{>D={ zsc8nk`V#5^!?_eiWW1On;6fFb>x&>_zRny$}Cl(+OU{?NP5w{ zw?RC^7215yphF3)O2A8_e5~Lh<5)&Py4ND_IK%^7oQt)Z>zQn zS&{*E+-y~OqwkeV+VQvX`mrU>NF1rQy_z$=F9sxScBsWBp?{z7#BJ{2`JV6Unh|SK zZ9@{C^p6xDTk}a4!3-J?Hfp?}%l{DB;E^r#aY<$8TF>-*He4FVi@L^K%f(1UY_zrA z*|1*C@r~=@TynEzlpgHpyuJ`NAaIUkUP^?NiBpQ9&!pheK2}PTozdVx`E1FW?TpUG zaiX1wH7%tH?)v0R{n-Z%I5BJI16+%iHRzca=|f^m=!EGb&v3 z%`Z!4al3yO1t500R|M%lkX)R9la4PtyHfF9Wbqlrt+`~#J@YKnFZP@#2RnF5B#G_D zSW>i^9YpOe0daPQNJolrolJIR0q|-mhyh~>L+dd`wad>#qUYXHKmD_#qf=*`Js71Ym39!TRQ^c27PX_XAkrP zG6xkMyR{e$!ZyCDFUF*w@lA44dGlu|&`g?Rofg3(nALOcctT?bba9qkzvua%OaBO- z;9~^5XaKzuq$LKPKYo^Y=q)bM&_Xie!lbRKr2nqm$`jQI6*C;~mlei(>_cY6W*uGi z<=hFP&Y1QBmb_vyNyg<~Eb8O$`j-~vnK?3A+*8;2mP0C_|Sc>SiPP zM2t2tw~-`zt@fDVtkRT6-Q02HXuS=~E-*^}sb0i%Ndj~I{7%3$$PfO=eJaksXI(oQEXw_F$H-jDDhQb4->Dp18K42h%?-Szg#-8%=%d z0*o@cU8pl28!1{$AV5NB1{>20LjQhM?hD<5r(sxDN{=H+Fvv0GnZ)L)U6FzPQ&4qL zx=w#qGc!Cg1agM=P!5>IRVvM*ozA;iv2I8gA-6uc?xdH-30uJi?uK<*RVe)9{MYcw zAqnAX*uC9>dTtq3d=ntc0@WwC<~+XTjBDIg)mhJ9a5}r04(e$6joLpl($o~U^&@Rb zm*EMm-fcF*hKG=IzMFDcT|@deH(Ns@oaP3*LWcqA0vZ?jd$qigE0+2+Lu$I^!%Yj9aN<60|ANJs~(q zJ9XSUCI3^EQu`@fzo0kAVr(<(#UyZ~6hra`W8oCgklR}I-$ujm9DfTSAZek6{SEk3 zifymx5n_JP!0VlibWfxoCdy+|V1pd@aT!!>EikW6zDV>E`BB}m zU0ljRE-rLVdCgbScRVF{L?l$#oI3sIM2Z|s@-kihBsm-Uq6V;RFm)NNvfA06Z(S(y z)YyAVUh<2}@ndHIZk-~YWt49Ibi&1$>c+Foj&=kCd5#U75tw=S0Hse)3J)r##3Ao+0zAuI$=9Ytpl2eQ0nb1t?OKGb!^jH%~qJC$lW0cP9@ZfsK@@99-Li| zFA&~y>wPT?_}+oQx?p|B3Y!4!YvZB`R{4*p=XN!;O{?JVroIaKRkQ!y71}}G32SiX zTgXj}zMNy?XTa{bp*86AmErVYQ{FsI)l9&?aOy^04X7qZ1Hrbp9~+M*iu^&L1a`t3 zL1C{R?puhEa5DBfC7SfvwwE26r-_Rfuk+E2`Ths^U>h8hs6Tf<%l>8oD3T>FA)pcS zF*cP|_WrS*s$6JY$SK=t`9S$fjB!#K-RTxY_P7-C3e<6_onpZb`mXA%Dw9&(=q9u_ zQpr=Tb3#c~pKp44HYu*lqty7_49zX50 zmF(W91jU%BAlY3R5i^^an-u zQ3mNcK~(L9kUnw|FUZ5o7FSU%=Uu1%X4faVLYKVMn%tOgK~9}#ceL1YE0ao$qV!P#=D;=zvK^s3&6Cai;Yh*MYczK zCLIrye-sW3@#%DY0%D%T2Tml2N^6f4RZ1&pg2~SfPxw}YcOxzH$nEwy`a(yjThMTV s { mockUserRepository.persist = jest.fn((test) =>{ return test }) - +mockUserRepository.getPreviewPath = jest.fn((test) => null) +mockUserRepository.addPreviewPath = jest.fn((test,test2) => null) +mockUserRepository.getByUser = jest.fn((test) => { + console.log("test") + return 1 +}) mockAccesTokenManager.generate = ((test) =>{return ''}) describe('user route', () => { beforeEach(async () => { + server = Hapi.server({ port: process.env.PORT || 3000 }); - await server.register([ - require('../../lib/interfaces/routes/users'), - ]); - server.register(Jwt) - server.app.serviceLocator = { userRepository: mockUserRepository, accessTokenManager:mockAccesTokenManager } + server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + await server.register([ + require('../../lib/interfaces/routes/users'), + ]); + + + }); afterEach(async () => { @@ -323,4 +334,33 @@ describe('user route', () => { expect(res.statusCode).toBe(400); }) }) + describe("/users/uploadPreviewProfile", () =>{ + it("should respond code 200", async () =>{ + const form = new FormData(); + server.app.serviceLocator.accessTokenManager.decode = jest.fn((test) => ({ value: 1 })); + const currentDirectory = process.cwd(); + const imageBuffer = fs.createReadStream(currentDirectory+'\\test\\integration\\fixture\\imageTest.jpg'); + await form.append('file', imageBuffer); + console.log(form.getHeaders()) + + const res = server.inject({ + method: 'POST', + url: '/users/uploadPreviewProfile', + payload: form, + headers: { + Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDEwMDgwNzV9.-n_G0pKIBI9jkUD56kYjby03tFnqYLttc_Z51eLvhKU', + ...form.getHeaders() + } , + }) + console.log(res) + res.then(null,(error) => console.log(error)) + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + console.log('Hello'); + await sleep(20000); + expect(res.statusCode).toBe(401) + }) + }) }); diff --git a/test/unit/application/usecase/user/uploadPreview.test.js b/test/unit/application/usecase/user/uploadPreview.test.js new file mode 100644 index 0000000..b8ea762 --- /dev/null +++ b/test/unit/application/usecase/user/uploadPreview.test.js @@ -0,0 +1,80 @@ +mockAccessTokenManager = {} +mockUserRepository = {} +mockDocumentRepository = {} +mockID = 12424 +const uploadPreview = require("../../../../../lib/application/use_cases/user/uploadPreview") +const mockFile = { + hapi: { + filename: "test.png", + headers: { + 'content-type' : 'image/png' + } + }, + _data: "testDFata" +} +mockAccessTokenManager.decode = jest.fn((token) => mockID) +mockUserRepository.getByUser = jest.fn((id) => 'something') +mockUserRepository.addPreviewPath = jest.fn((id,path) => {}) +mockDocumentRepository.deleteFile = jest.fn((path) => path) +mockUserRepository.getPreviewPath = jest.fn((id) => null) +mockDocumentRepository.uploadFile = jest.fn((path,file) => path) +describe("uploadPreview usecase", ()=>{ + describe("valid upload preview cases", ()=>{ + it('should upload the image without deleting previous file',async() =>{ + mockUserRepository.getPreviewPath = jest.fn((id) => null) + const path = await uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + }) + expect(path).not.toBeNull() + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0) + } ) + it('should upload the image and deleting previous file',async() =>{ + mockUserRepository.getPreviewPath = jest.fn((id) => 'path') + const path = await uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + }) + expect(path).not.toBeNull() + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1) + } ) + }) + describe("invalid upload preview cases", ()=> { + + const invalidMockFile = { + hapi: { + filename: "test.rpg", + headers: { + 'content-type' : 'image/rpg' + } + }, + _data: "testDFata" + } + it('should throw 415 error',async() =>{ + await expect(uploadPreview(invalidMockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + })).rejects.toThrow("le fichier fourni n'est pas une image") + } ) + it('should throw 500 error',async() =>{ + mockDocumentRepository.uploadFile = jest.fn((path,file) => null) + await expect(uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + })).rejects.toThrow("internal server error") + } ) + it('should throw 401 error',async() =>{ + mockUserRepository.getByUser = jest.fn((id) => null) + await expect(uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + })).rejects.toThrow("votre token d'authentification n'est pas le bon") + } ) + }) + +}) \ No newline at end of file diff --git a/test/unit/infrastructure/repositories/DocumentRepository.test.js b/test/unit/infrastructure/repositories/DocumentRepository.test.js new file mode 100644 index 0000000..4f25fed --- /dev/null +++ b/test/unit/infrastructure/repositories/DocumentRepository.test.js @@ -0,0 +1,28 @@ +const DocumentRepositoryTest = require("../../../../lib/infrastructure/repositories/DocumentRepository") +const createUser = require("../../../../lib/application/use_cases/user/CreateUser"); +let filePath +const documentRepository = new DocumentRepositoryTest(); +const mockFile = { + hapi: { + filename: "test.png" + }, + _data: "testDFata" +} +describe('document repository', ()=>{ + describe('document repository with no problem', ()=>{ + it('should create file without problem', async () =>{ + filePath= await documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder", mockFile) + }) + it('should creadzadte file without problem', async () =>{ + documentRepository.deleteFile(filePath) + }) + }) + describe('document repository with errors', () =>{ + it("shouldn't create file", async () =>{ + await expect(documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder",{})).rejects.toThrow('Cannot read property \'filename\' of undefined') + }) + it("shouldn't delete file", async () =>{ + await expect(documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder/test.png",{})).rejects.toThrow('Cannot read property \'filename\' of undefined') + }) + }) +}) \ No newline at end of file diff --git a/test/unit/infrastructure/repositories/testFolder/1593d51b7bec762dc17fbc8833c18c7c.png b/test/unit/infrastructure/repositories/testFolder/1593d51b7bec762dc17fbc8833c18c7c.png new file mode 100644 index 0000000..84decf0 --- /dev/null +++ b/test/unit/infrastructure/repositories/testFolder/1593d51b7bec762dc17fbc8833c18c7c.png @@ -0,0 +1 @@ +testDFata \ No newline at end of file From 1c2236538fc917aa2b42037ee8b1ad7d7edec929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 4 Dec 2023 12:19:13 +0100 Subject: [PATCH 04/67] version updating (#10) --- lib/infrastructure/webserver/server.js | 43 +- package-lock.json | 11239 ++++++++++------------- package.json | 19 +- 3 files changed, 4694 insertions(+), 6607 deletions(-) diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 6c779c7..7ffc0b9 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -1,7 +1,6 @@ 'use strict'; const Hapi = require('@hapi/hapi'); -const Good = require('@hapi/good'); const Inert = require('@hapi/inert'); const Vision = require('@hapi/vision'); const Blipp = require('blipp'); @@ -32,27 +31,27 @@ const createServer = async () => { }, } }, - { - plugin: Good, - options: { - ops: { - interval: 1000 * 60 - }, - reporters: { - myConsoleReporter: [ - { - module: '@hapi/good-squeeze', - name: 'Squeeze', - args: [{ ops: '*', log: '*', error: '*', response: '*' }] - }, - { - module: '@hapi/good-console' - }, - 'stdout' - ] - } - }, - }, + // { + // plugin: Good, + // options: { + // ops: { + // interval: 1000 * 60 + // }, + // reporters: { + // myConsoleReporter: [ + // { + // module: '@hapi/good-squeeze', + // name: 'Squeeze', + // args: [{ ops: '*', log: '*', error: '*', response: '*' }] + // }, + // { + // module: '@hapi/good-console' + // }, + // 'stdout' + // ] + // } + // }, + // }, { plugin: Jwt } diff --git a/package-lock.json b/package-lock.json index 8b62635..13b8512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,842 +1,1184 @@ { "name": "nodejs-clean-architecture-app", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } - }, - "@babel/core": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.1.tgz", - "integrity": "sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helpers": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1", - "convert-source-map": "^1.7.0", + "packages": { + "": { + "name": "nodejs-clean-architecture-app", + "version": "1.0.0", + "dependencies": { + "@hapi/boom": "^9.1.0", + "@hapi/good": "^9.0.1", + "@hapi/good-console": "^9.0.1", + "@hapi/good-squeeze": "^6.0.0", + "@hapi/hapi": "^21.3.2", + "@hapi/hoek": "^11.0.2", + "@hapi/joi": "^17.1.1", + "@hapi/jwt": "^3.2.0", + "@hapi/shot": "^6.0.1", + "@hapi/vision": "^7.0.3", + "axios": "^1.5.1", + "bcrypt": "^5.1.1", + "blipp": "^4.0.2", + "dotenv": "^8.6.0", + "hapi-swagger": "^17.2.0", + "joi": "^17.10.0", + "jsonwebtoken": "^8.5.1", + "mysql2": "^3.6.0", + "request": "^2.88.2", + "require-directory": "^2.1.1", + "sequelize": "^5.21.11", + "underscore": "^1.13.6" + }, + "devDependencies": { + "husky": "^8.0.3", + "jest": "^28.0.0", + "nodemon": "^2.0.4", + "sequelize-mock": "^0.10.2" + }, + "engines": { + "node": ">=12", + "npm": ">=6.12" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.1.0.tgz", + "integrity": "sha512-g/VW9ZQEFJAOwAyUb8JFf7MLiLy2uEB4rU270rGzDwICxnxMlPy0O11KVePSgS36K1NI29gSlK84n5INGhd4Ag==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.13", + "@types/lodash.clonedeep": "^4.5.7", + "js-yaml": "^4.1.0", + "lodash.clonedeep": "^4.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@apidevtools/swagger-parser/node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/swagger-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@apidevtools/swagger-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@babel/generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.1.tgz", - "integrity": "sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", "dev": true, - "requires": { - "@babel/types": "^7.10.1", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "dependencies": { + "yallist": "^3.0.2" } }, - "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "requires": { - "@babel/types": "^7.10.1" + "bin": { + "semver": "bin/semver.js" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", - "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "requires": { - "@babel/types": "^7.10.1" + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-imports": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz", - "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "requires": { - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-module-transforms": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", - "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", - "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, - "requires": { - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz", - "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", - "dev": true + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "@babel/helper-replace-supers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", - "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-simple-access": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", - "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, - "requires": { - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "requires": { - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/helpers": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", - "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "node_modules/@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", "dev": true, - "requires": { - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" } }, - "@babel/parser": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz", - "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==", + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "@babel/plugin-syntax-async-generators": { + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-bigint": { + "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz", - "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-json-strings": { + "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.1.tgz", - "integrity": "sha512-XyHIFa9kdrgJS91CUH+ccPVTnJShr8nLGc5bG2IhGXv5p1Rd+8BleGE5yzIg2Nc1QZAdHDa0Qp4m6066OL96Iw==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz", - "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-object-rest-spread": { + "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-optional-catch-binding": { + "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/plugin-syntax-optional-chaining": { + "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "node_modules/@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@babel/types": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz", - "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==", + "node_modules/@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@bcoe/v8-coverage": { + "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" + "node_modules/@hapi/accept": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz", + "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "@hapi/accept": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.2.tgz", - "integrity": "sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/accept/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/address": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.2.tgz", - "integrity": "sha512-O4QDrx+JoGKZc6aN64L04vqa7e41tIiLU+OvKdcYaEMP97UttL0f9GIi9/0A4WAMx0uBd6SidDIhktZhgOcN8Q==" - }, - "@hapi/ammo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-5.0.1.tgz", - "integrity": "sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==", - "requires": { - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/address": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", + "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", + "deprecated": "Moved to 'npm install @sideway/address'", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - } + "@hapi/hoek": "^9.0.0" } }, - "@hapi/b64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-5.0.0.tgz", - "integrity": "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==", - "requires": { - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/address/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/ammo": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz", + "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/b64": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", + "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/boom": { + "node_modules/@hapi/boom": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.0.tgz", "integrity": "sha512-4nZmpp4tXbm162LaZT45P7F7sgiem8dwAh2vHWT6XX24dozNjGMg6BvKCRvtCUcmcXqeMIUqWN8Rc5X8yKuROQ==", - "requires": { + "dependencies": { "@hapi/hoek": "9.x.x" - }, + } + }, + "node_modules/@hapi/boom/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/bounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.1.tgz", + "integrity": "sha512-G+/Pp9c1Ha4FDP+3Sy/Xwg2O4Ahaw3lIZFSX+BL4uWi64CmiETuZPxhKDUD4xBMOUZbBlzvO8HjiK8ePnhBadA==", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - } + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "@hapi/bounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", - "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/bounce/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/bourne": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", - "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==" }, - "@hapi/call": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@hapi/call/-/call-8.0.1.tgz", - "integrity": "sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/call": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz", + "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "@hapi/catbox": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.1.1.tgz", - "integrity": "sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/podium": "4.x.x", - "@hapi/validate": "1.x.x" - }, + "node_modules/@hapi/call/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/catbox-memory": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz", - "integrity": "sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/catbox": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", + "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/podium": "^5.0.0", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/catbox-memory": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.1.tgz", + "integrity": "sha512-sVb+/ZxbZIvaMtJfAbdyY+QJUQg9oKTwamXpEg/5xnfG5WbJLTjvEn4kIGKz9pN3ENNbIL/bIdctmHmqi/AdGA==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2" } }, - "@hapi/catbox-object": { + "node_modules/@hapi/catbox-memory/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/catbox-object": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@hapi/catbox-object/-/catbox-object-3.0.1.tgz", "integrity": "sha512-3w6E2DXtjWbmLYi4WcFUOor5jgrXN4PWhDrMrXKP/cEsFSfVSRJ0FhY2PXrhrUHwcllfKezYafWU3tQ5+8RO1w==", - "requires": { + "dependencies": { "@hapi/boom": "^10.0.1", "@hapi/hoek": "^11.0.2" - }, + } + }, + "node_modules/@hapi/catbox-object/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "requires": { - "@hapi/hoek": "^11.0.2" - } - }, - "@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/content": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", - "integrity": "sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==", - "requires": { - "@hapi/boom": "9.x.x" + "node_modules/@hapi/catbox/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/cryptiles": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.1.0.tgz", - "integrity": "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==", - "requires": { - "@hapi/boom": "9.x.x" + "node_modules/@hapi/catbox/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", - "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==" + "node_modules/@hapi/catbox/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/content": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.0.tgz", + "integrity": "sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA==", + "dependencies": { + "@hapi/boom": "^10.0.0" + } + }, + "node_modules/@hapi/content/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/cryptiles": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.1.tgz", + "integrity": "sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ==", + "dependencies": { + "@hapi/boom": "^10.0.1" + }, + "engines": { + "node": ">=14.0.0" + } }, - "@hapi/formula": { + "node_modules/@hapi/cryptiles/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", + "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==" + }, + "node_modules/@hapi/formula": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", + "deprecated": "Moved to 'npm install @sideway/formula'" }, - "@hapi/good": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@hapi/good/-/good-9.0.0.tgz", - "integrity": "sha512-jt6mEzFfY+jzE/IbvNVTTHcqKE9RP609MXKff1Pj4VPCnCSG8UVUtTdr1nM6UFN02NvntqShlpeZi4o+RgN35g==", - "requires": { + "node_modules/@hapi/good": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/good/-/good-9.0.1.tgz", + "integrity": "sha512-zHSjw+LieqDgu5pXLUHqUJTMSgBKIdKHwxmXNoh9qVz+kSPQH463ol6OEBSTVdy0yQawKVI4eAyeqown++TpbA==", + "dependencies": { "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", "@hapi/oppsy": "3.x.x", - "pumpify": "1.x.x" - }, - "dependencies": { - "@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } + "@hapi/validate": "1.x.x", + "pumpify": "2.x.x" } }, - "@hapi/good-console": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@hapi/good-console/-/good-console-9.0.0.tgz", - "integrity": "sha512-f0+eFyKGfagfCvyjFm3C2vFmL+4/fsSJ6bgf6LbBf3GV918hZAl40RUCRyiPKdYmEoTAKcq++Ti9xe1oOVDKdA==", - "requires": { + "node_modules/@hapi/good-console": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@hapi/good-console/-/good-console-9.0.1.tgz", + "integrity": "sha512-MbkHPDJjm2SegDG1Sb4Jtvw6uPuMbvMiPFUAQwUO0a08UFwKWt4qZ8rCV/tjoXRLEVh6CHSn45MZM4SU2Pg79g==", + "dependencies": { "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", "json-stringify-safe": "5.x.x", "moment": "2.x.x" - }, - "dependencies": { - "@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } } }, - "@hapi/good-squeeze": { + "node_modules/@hapi/good-console/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/good-squeeze": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/good-squeeze/-/good-squeeze-6.0.0.tgz", "integrity": "sha512-UgHAF9Lm8fJPzgf2HymtowOwNc1+IL+p08YTVR+XA4d8nmyE1t9x3RLA4riqldnOKHkVqGakJ1jGqUG7jk77Cg==", - "requires": { + "dependencies": { "@hapi/hoek": "9.x.x", "fast-safe-stringify": "2.x.x" - }, + } + }, + "node_modules/@hapi/good-squeeze/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/good/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/hapi": { + "version": "21.3.2", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.3.2.tgz", + "integrity": "sha512-tbm0zmsdUj8iw4NzFV30FST/W4qzh/Lsw6Q5o5gAhOuoirWvxm8a4G3o60bqBw8nXvRNJ8uLtE0RKLlZINxHcQ==", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - } + "@hapi/accept": "^6.0.1", + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/call": "^9.0.1", + "@hapi/catbox": "^12.1.1", + "@hapi/catbox-memory": "^6.0.1", + "@hapi/heavy": "^8.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/mimos": "^7.0.1", + "@hapi/podium": "^5.0.1", + "@hapi/shot": "^6.0.1", + "@hapi/somever": "^4.1.1", + "@hapi/statehood": "^8.1.1", + "@hapi/subtext": "^8.1.0", + "@hapi/teamwork": "^6.0.0", + "@hapi/topo": "^6.0.1", + "@hapi/validate": "^2.0.1" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@hapi/hapi/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/hapi": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-20.3.0.tgz", - "integrity": "sha512-zvPSRvaQyF3S6Nev9aiAzko2/hIFZmNSJNcs07qdVaVAvj8dGJSV4fVUuQSnufYJAGiSau+U5LxMLhx79se5WA==", - "requires": { - "@hapi/accept": "^5.0.1", - "@hapi/ammo": "^5.0.1", - "@hapi/boom": "^9.1.0", - "@hapi/bounce": "^2.0.0", - "@hapi/call": "^8.0.0", - "@hapi/catbox": "^11.1.1", - "@hapi/catbox-memory": "^5.0.0", - "@hapi/heavy": "^7.0.1", - "@hapi/hoek": "^9.0.4", - "@hapi/mimos": "^6.0.0", - "@hapi/podium": "^4.1.1", - "@hapi/shot": "^5.0.5", - "@hapi/somever": "^3.0.0", - "@hapi/statehood": "^7.0.3", - "@hapi/subtext": "^7.1.0", - "@hapi/teamwork": "^5.1.0", - "@hapi/topo": "^5.0.0", - "@hapi/validate": "^1.1.1" - }, + "node_modules/@hapi/hapi/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/heavy": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-7.0.1.tgz", - "integrity": "sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/validate": "1.x.x" - }, + "node_modules/@hapi/hapi/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" } }, - "@hapi/hoek": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.3.2.tgz", - "integrity": "sha512-NP5SG4bzix+EtSMtcudp8TvI0lB46mXNo8uFpTDw6tqxGx4z5yx+giIunEFA0Z7oUO4DuWrOJV9xqR2tJVEdyA==" + "node_modules/@hapi/heavy": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz", + "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } }, - "@hapi/inert": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/inert/-/inert-6.0.1.tgz", - "integrity": "sha512-oLxAmtWni3nH4INU2gcXFnHBw0GhHYF3HR71hAWrPc91dq+iFYGfawfaMbonwGr5DkzFiGe8Ir5sZAt2AqeINA==", - "requires": { - "@hapi/ammo": "5.x.x", - "@hapi/boom": "9.x.x", - "@hapi/bounce": "2.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", - "lru-cache": "5.x.x" - }, + "node_modules/@hapi/heavy/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/iron": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-6.0.0.tgz", - "integrity": "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==", - "requires": { - "@hapi/b64": "5.x.x", - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/cryptiles": "5.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/heavy/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/bourne": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", - "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==" - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/joi": { + "node_modules/@hapi/heavy/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/hoek": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", + "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" + }, + "node_modules/@hapi/iron": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", + "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==", + "dependencies": { + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/iron/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/joi": { "version": "17.1.1", "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { + "deprecated": "Switch to 'npm install joi'", + "dependencies": { "@hapi/address": "^4.0.1", "@hapi/formula": "^2.0.0", "@hapi/hoek": "^9.0.0", "@hapi/pinpoint": "^2.0.0", "@hapi/topo": "^5.0.0" - }, - "dependencies": { - "@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } } }, - "@hapi/jwt": { + "node_modules/@hapi/joi/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/jwt": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-3.2.0.tgz", "integrity": "sha512-2EU5zdr0petG63J2RHiagGByA1Qr6qzxMmrJJ3/0cm78be1Lq4vi038YgKJvbBSxSboIp5SWGZdYB6+CJlqEoQ==", - "requires": { + "dependencies": { "@hapi/b64": "^6.0.0", "@hapi/boom": "^10.0.0", "@hapi/bounce": "^3.0.0", @@ -847,1014 +1189,695 @@ "@hapi/wreck": "^18.0.0", "ecdsa-sig-formatter": "^1.0.0", "joi": "^17.2.1" - }, + } + }, + "node_modules/@hapi/jwt/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/b64": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz", - "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==", - "requires": { - "@hapi/hoek": "^11.0.2" - }, - "dependencies": { - "@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - } - } - }, - "@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "requires": { - "@hapi/hoek": "^11.0.2" - }, - "dependencies": { - "@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - } - } - }, - "@hapi/bounce": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.1.tgz", - "integrity": "sha512-G+/Pp9c1Ha4FDP+3Sy/Xwg2O4Ahaw3lIZFSX+BL4uWi64CmiETuZPxhKDUD4xBMOUZbBlzvO8HjiK8ePnhBadA==", - "requires": { - "@hapi/boom": "^10.0.1", - "@hapi/hoek": "^11.0.2" - }, - "dependencies": { - "@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - } - } - }, - "@hapi/bourne": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", - "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==" - }, - "@hapi/cryptiles": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.1.tgz", - "integrity": "sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ==", - "requires": { - "@hapi/boom": "^10.0.1" - } - }, - "@hapi/hoek": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-10.0.1.tgz", - "integrity": "sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw==" - }, - "@hapi/wreck": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.0.1.tgz", - "integrity": "sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg==", - "requires": { - "@hapi/boom": "^10.0.1", - "@hapi/bourne": "^3.0.0", - "@hapi/hoek": "^11.0.2" - }, - "dependencies": { - "@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - } - } - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/mimos": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-6.0.0.tgz", - "integrity": "sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg==", - "requires": { - "@hapi/hoek": "9.x.x", - "mime-db": "1.x.x" - }, + "node_modules/@hapi/jwt/node_modules/@hapi/boom/node_modules/@hapi/hoek": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", + "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" + }, + "node_modules/@hapi/jwt/node_modules/@hapi/hoek": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-10.0.1.tgz", + "integrity": "sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw==" + }, + "node_modules/@hapi/mimos": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz", + "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "mime-db": "^1.52.0" } }, - "@hapi/nigel": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-4.0.2.tgz", - "integrity": "sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw==", - "requires": { - "@hapi/hoek": "^9.0.4", - "@hapi/vise": "^4.0.0" - }, + "node_modules/@hapi/nigel": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz", + "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/vise": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" } }, - "@hapi/oppsy": { + "node_modules/@hapi/oppsy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/oppsy/-/oppsy-3.0.0.tgz", "integrity": "sha512-0kfUEAqIi21GzFVK2snMO07znMEBiXb+/pOx1dmgOO9TuvFstcfmHU5i56aDfiFP2DM5WzQCU2UWc2gK1lMDhQ==", - "requires": { + "dependencies": { "@hapi/hoek": "9.x.x" - }, + } + }, + "node_modules/@hapi/oppsy/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/pez": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.0.tgz", + "integrity": "sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg==", "dependencies": { - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - } + "@hapi/b64": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/content": "^6.0.0", + "@hapi/hoek": "^11.0.2", + "@hapi/nigel": "^5.0.1" } }, - "@hapi/pez": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.1.0.tgz", - "integrity": "sha512-YfB0btnkLB3lb6Ry/1KifnMPBm5ZPfaAHWFskzOMAgDgXgcBgA+zjpIynyEiBfWEz22DBT8o1e2tAaBdlt8zbw==", - "requires": { - "@hapi/b64": "5.x.x", - "@hapi/boom": "9.x.x", - "@hapi/content": "^5.0.2", - "@hapi/hoek": "9.x.x", - "@hapi/nigel": "4.x.x" - }, + "node_modules/@hapi/pez/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/podium": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.1.3.tgz", - "integrity": "sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g==", - "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/teamwork": "5.x.x", - "@hapi/validate": "1.x.x" - }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==" + }, + "node_modules/@hapi/podium": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.1.tgz", + "integrity": "sha512-eznFTw6rdBhAijXFIlBOMJJd+lXTvqbrBIS4Iu80r2KTVIo4g+7fLy4NKp/8+UnSt5Ox6mJtAlKBU/Sf5080TQ==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/teamwork": "^6.0.0", + "@hapi/validate": "^2.0.1" } }, - "@hapi/shot": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-5.0.5.tgz", - "integrity": "sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A==", - "requires": { - "@hapi/hoek": "9.x.x", - "@hapi/validate": "1.x.x" - }, + "node_modules/@hapi/podium/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/somever": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-3.0.1.tgz", - "integrity": "sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w==", - "requires": { - "@hapi/bounce": "2.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/podium/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" } }, - "@hapi/statehood": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-7.0.4.tgz", - "integrity": "sha512-Fia6atroOVmc5+2bNOxF6Zv9vpbNAjEXNcUbWXavDqhnJDlchwUUwKS5LCi5mGtCTxRhUKKHwuxuBZJkmLZ7fw==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bounce": "2.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/cryptiles": "5.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/iron": "6.x.x", - "@hapi/validate": "1.x.x" - }, + "node_modules/@hapi/shot": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.1.tgz", + "integrity": "sha512-s5ynMKZXYoDd3dqPw5YTvOR/vjHvMTxc388+0qL0jZZP1+uwXuUD32o9DuuuLsmTlyXCWi02BJl1pBpwRuUrNA==", "dependencies": { - "@hapi/bourne": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", - "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==" - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" } }, - "@hapi/subtext": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-7.1.0.tgz", - "integrity": "sha512-n94cU6hlvsNRIpXaROzBNEJGwxC+HA69q769pChzej84On8vsU14guHDub7Pphr/pqn5b93zV3IkMPDU5AUiXA==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/content": "^5.0.2", - "@hapi/file": "2.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/pez": "^5.1.0", - "@hapi/wreck": "17.x.x" - }, + "node_modules/@hapi/shot/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/bourne": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", - "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==" - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/shot/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/somever": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz", + "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==", + "dependencies": { + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.1.1.tgz", + "integrity": "sha512-YbK7PSVUA59NArAW5Np0tKRoIZ5VNYUicOk7uJmWZF6XyH5gGL+k62w77SIJb0AoAJ0QdGQMCQ/WOGL1S3Ydow==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/cryptiles": "^6.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/iron": "^7.0.1", + "@hapi/validate": "^2.0.1" + } + }, + "node_modules/@hapi/statehood/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/statehood/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/subtext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.0.tgz", + "integrity": "sha512-PyaN4oSMtqPjjVxLny1k0iYg4+fwGusIhaom9B2StinBclHs7v46mIW706Y+Wo21lcgulGyXbQrmT/w4dus6ww==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/content": "^6.0.0", + "@hapi/file": "^3.0.0", + "@hapi/hoek": "^11.0.2", + "@hapi/pez": "^6.1.0", + "@hapi/wreck": "^18.0.1" + } + }, + "node_modules/@hapi/subtext/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/teamwork": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.0.tgz", + "integrity": "sha512-05HumSy3LWfXpmJ9cr6HzwhAavrHkJ1ZRCmNE2qJMihdM5YcWreWPfyN0yKT2ZjCM92au3ZkuodjBxOibxM67A==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@hapi/topo/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/validate": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-1.1.3.tgz", + "integrity": "sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "node_modules/@hapi/validate/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/vise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", + "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/teamwork": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-5.1.1.tgz", - "integrity": "sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg==" + "node_modules/@hapi/vision": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@hapi/vision/-/vision-7.0.3.tgz", + "integrity": "sha512-1UM3Xej7HZQPaxzWkefvMfcuXoF9R8kIiDTl+Pfdv8f5mJwAv0zIB4R/UvNoQP1+JYgQT+QeUDxcGD8QdIUDyg==", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1" + } }, - "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", - "requires": { - "@hapi/hoek": "^8.3.0" + "node_modules/@hapi/vision/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "@hapi/validate": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-1.1.3.tgz", - "integrity": "sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==", - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0" - }, + "node_modules/@hapi/vision/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } + "@hapi/hoek": "^11.0.2" } }, - "@hapi/vise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-4.0.0.tgz", - "integrity": "sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==", - "requires": { - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/vision/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" } }, - "@hapi/vision": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/vision/-/vision-6.0.0.tgz", - "integrity": "sha512-NRG87SLS8evCTwAGlVHykmlk1i9z+7TD2pUwoDHRpjwqX/syt0ecrEKMLV/VdMijE9tCAtKIIXt+Myb16tT7Sw==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bounce": "2.x.x", - "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x" - }, + "node_modules/@hapi/wreck": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.0.1.tgz", + "integrity": "sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg==", "dependencies": { - "@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/bounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", - "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" } }, - "@hapi/wreck": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.2.0.tgz", - "integrity": "sha512-pJ5kjYoRPYDv+eIuiLQqhGon341fr2bNIYZjuotuPJG/3Ilzr/XtI+JAp0A86E2bYfsS3zBPABuS2ICkaXFT8g==", - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/bourne": "2.x.x", - "@hapi/hoek": "9.x.x" - }, + "node_modules/@hapi/wreck/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/bourne": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz", - "integrity": "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==" - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^11.0.2" } }, - "@istanbuljs/load-nyc-config": { + "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "@jest/console": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.0.1.tgz", - "integrity": "sha512-9t1KUe/93coV1rBSxMmBAOIK3/HVpwxArCA1CxskKyRiv6o8J70V8C/V3OJminVCTa2M0hQI9AWRd5wxu2dAHw==", + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^26.0.1", - "jest-util": "^26.0.1", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", "slash": "^3.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/core": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.0.1.tgz", - "integrity": "sha512-Xq3eqYnxsG9SjDC+WLeIgf7/8KU6rddBxH+SCt18gEpOhAGYC/Mq+YbtlNcIdwjnnT+wDseXSbU0e5X84Y4jTQ==", + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", "dev": true, - "requires": { - "@jest/console": "^26.0.1", - "@jest/reporters": "^26.0.1", - "@jest/test-result": "^26.0.1", - "@jest/transform": "^26.0.1", - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.0.1", - "jest-config": "^26.0.1", - "jest-haste-map": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.0.1", - "jest-resolve-dependencies": "^26.0.1", - "jest-runner": "^26.0.1", - "jest-runtime": "^26.0.1", - "jest-snapshot": "^26.0.1", - "jest-util": "^26.0.1", - "jest-validate": "^26.0.1", - "jest-watcher": "^26.0.1", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" } + ], + "engines": { + "node": ">=8" } }, - "@jest/environment": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.0.1.tgz", - "integrity": "sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g==", + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, - "requires": { - "@jest/fake-timers": "^26.0.1", - "@jest/types": "^26.0.1", - "jest-mock": "^26.0.1" + "dependencies": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "dependencies": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/fake-timers": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.0.1.tgz", - "integrity": "sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg==", + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "@sinonjs/fake-timers": "^6.0.1", - "jest-message-util": "^26.0.1", - "jest-mock": "^26.0.1", - "jest-util": "^26.0.1" + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/globals": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.0.1.tgz", - "integrity": "sha512-iuucxOYB7BRCvT+TYBzUqUNuxFX1hqaR6G6IcGgEqkJ5x4htNKo1r7jk1ji9Zj8ZMiMw0oB5NaA7k5Tx6MVssA==", + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", "dev": true, - "requires": { - "@jest/environment": "^26.0.1", - "@jest/types": "^26.0.1", - "expect": "^26.0.1" + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/reporters": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.0.1.tgz", - "integrity": "sha512-NWWy9KwRtE1iyG/m7huiFVF9YsYv/e+mbflKRV84WDoJfBqUrNRyDbL/vFxQcYLl8IRqI4P3MgPn386x76Gf2g==", + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", "dev": true, - "requires": { + "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.0.1", - "@jest/test-result": "^26.0.1", - "@jest/transform": "^26.0.1", - "@jest/types": "^26.0.1", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.0.1", - "jest-resolve": "^26.0.1", - "jest-util": "^26.0.1", - "jest-worker": "^26.0.0", - "node-notifier": "^7.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", "slash": "^3.0.0", - "source-map": "^0.6.0", "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true } } }, - "@jest/source-map": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.0.0.tgz", - "integrity": "sha512-S2Z+Aj/7KOSU2TfW0dyzBze7xr95bkm5YXNUqqCek+HE0VbNNSNzrRwfIi5lf7wvzDTSS0/ib8XQ1krFNyYgbQ==", + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" + "dependencies": { + "@sinclair/typebox": "^0.24.1" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/test-result": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.0.1.tgz", - "integrity": "sha512-oKwHvOI73ICSYRPe8WwyYPTtiuOAkLSbY8/MfWF3qDEd/sa8EDyZzin3BaXTqufir/O/Gzea4E8Zl14XU4Mlyg==", + "node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", "dev": true, - "requires": { - "@jest/console": "^26.0.1", - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/test-sequencer": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz", - "integrity": "sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg==", + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", "dev": true, - "requires": { - "@jest/test-result": "^26.0.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.0.1", - "jest-runner": "^26.0.1", - "jest-runtime": "^26.0.1" - }, "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "@jest/transform": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.0.1.tgz", - "integrity": "sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA==", + "node_modules/@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.0.1", - "babel-plugin-istanbul": "^6.0.0", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.0.1", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.0.1", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "@jest/types": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", - "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", "dev": true, - "requires": { + "dependencies": { + "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@mapbox/node-pre-gyp": { + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "requires": { + "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", @@ -1865,733 +1888,637 @@ "semver": "^7.3.5", "tar": "^6.1.11" }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "requires": { - "abbrev": "1" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" } }, - "@sideway/address": { + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "requires": { - "@hapi/hoek": "^9.0.0" - }, "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - } + "@hapi/hoek": "^9.0.0" } }, - "@sideway/formula": { + "node_modules/@sideway/address/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@sideway/formula": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" }, - "@sideway/pinpoint": { + "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, - "@sindresorhus/is": { + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "dev": true + }, + "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, - "requires": { + "dependencies": { "type-detect": "4.0.8" } }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, - "requires": { + "dependencies": { "@sinonjs/commons": "^1.7.0" } }, - "@szmarczak/http-timer": { + "node_modules/@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", "dev": true, - "requires": { + "dependencies": { "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" } }, - "@types/babel__core": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", - "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, - "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "node_modules/@types/babel__generator": { + "version": "7.6.7", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", + "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", "dev": true, - "requires": { + "dependencies": { "@babel/types": "^7.0.0" } }, - "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, - "requires": { + "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, - "@types/babel__traverse": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", - "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", + "node_modules/@types/babel__traverse": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", "dev": true, - "requires": { - "@babel/types": "^7.3.0" + "dependencies": { + "@babel/types": "^7.20.7" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", - "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, - "requires": { + "dependencies": { "@types/node": "*" } }, - "@types/istanbul-lib-coverage": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz", - "integrity": "sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "requires": { + "dependencies": { "@types/istanbul-lib-coverage": "*" } }, - "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", + "dependencies": { "@types/istanbul-lib-report": "*" } }, - "@types/node": { + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.6.tgz", "integrity": "sha512-FbNmu4F67d3oZMWBV6Y4MaPER+0EpE9eIYf2yaHhCWovc1dlXCZkqGX4NLHfVVr6umt20TNBdRzrNJIzIKfdbw==" }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/prettier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz", - "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==", + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, - "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "@types/yargs": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", - "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, - "requires": { + "dependencies": { "@types/yargs-parser": "*" } }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "abbrev": { + "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-walk": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", - "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", - "dev": true - }, - "agent-base": { + "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { + "dependencies": { "debug": "4" }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "ajv": { + "node_modules/ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "requires": { + "dependencies": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, - "ansi-align": { + "node_modules/ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", "dev": true, - "requires": { + "dependencies": { "string-width": "^3.0.0" - }, + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" } }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "requires": { - "type-fest": "^0.11.0" + "dependencies": { + "ansi-regex": "^4.1.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "any-promise": { + "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "aproba": { + "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, - "are-we-there-yet": { + "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "requires": { + "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "engines": { + "node": ">=10" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { + "dev": true, + "dependencies": { "sprintf-js": "~1.0.2" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "asn1": { + "node_modules/asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { + "dependencies": { "safer-buffer": "~2.1.0" } }, - "assert-plus": { + "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } }, - "asynckit": { + "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { + "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } }, - "aws4": { + "node_modules/aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, - "axios": { + "node_modules/axios": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", - "requires": { + "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" - }, + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "babel-jest": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.1.tgz", - "integrity": "sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw==", + "node_modules/babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", "dev": true, - "requires": { - "@jest/transform": "^26.0.1", - "@jest/types": "^26.0.1", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.0.0", + "dependencies": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-plugin-jest-hoist": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz", - "integrity": "sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w==", + "node_modules/babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", "dev": true, - "requires": { + "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "babel-preset-current-node-syntax": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz", - "integrity": "sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, - "requires": { + "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.8.3", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "babel-preset-jest": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz", - "integrity": "sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw==", + "node_modules/babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^26.0.0", - "babel-preset-current-node-syntax": "^0.1.2" + "dependencies": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bcrypt": { + "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "requires": { + "hasInstallScript": true, + "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" } }, - "bcrypt-pbkdf": { + "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { + "dependencies": { "tweetnacl": "^0.14.3" } }, - "binary-extensions": { + "node_modules/binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "dev": true, + "engines": { + "node": ">=8" } }, - "blipp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/blipp/-/blipp-4.0.1.tgz", - "integrity": "sha512-nmtErzngVgJF6HlpnEymOil23m5U82oTYhbU8m619kQzj8yJ2q1ZFbL45i+dBcO92XTocyyj3QtC3GMxRujv8w==", - "requires": { - "@hapi/hoek": "8.x.x", - "@hapi/joi": "15.x.x", - "chalk": "2.x.x", - "easy-table": "1.x.x" - }, - "dependencies": { - "@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", - "requires": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" - } - } + "node_modules/blipp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/blipp/-/blipp-4.0.2.tgz", + "integrity": "sha512-QA5amT0IFJgCFgJeWw2udD2zZLui60NgqXTyvbSq+qpVbS6jfqELTRlC8PWW0yD4+chdZ2a+svnN6WE9zqfK5Q==", + "dependencies": { + "@hapi/hoek": "9.x.x", + "chalk": "4.x.x", + "easy-table": "1.x.x", + "joi": "17.x.x" } }, - "bluebird": { + "node_modules/blipp/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "boxen": { + "node_modules/boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", "dev": true, - "requires": { + "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", @@ -2601,151 +2528,126 @@ "type-fest": "^0.8.1", "widest-line": "^3.1.0" }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } }, - "bser": { + "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, - "requires": { + "dependencies": { "node-int64": "^0.4.0" } }, - "bson": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", - "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==" - }, - "buffer-equal-constant-time": { + "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cacheable-request": { + "node_modules/cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", "dev": true, - "requires": { + "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", @@ -2754,1028 +2656,816 @@ "normalize-url": "^4.1.0", "responselike": "^1.0.2" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "callsites": { + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, + "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "camelcase": { + "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", "dev": true, - "requires": { - "rsvp": "^4.8.4" + "engines": { + "node": ">=6" } }, - "caseless": { + "node_modules/caniuse-lite": { + "version": "1.0.30001566", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", + "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "char-regex": { + "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "chokidar": { + "node_modules/chokidar": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", "dev": true, - "requires": { + "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.4.0" }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.2" } }, - "chownr": { + "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } }, - "ci-info": { + "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true }, - "cli-boxes": { + "node_modules/cli-boxes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "requires": { + "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "clone": { + "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "optional": true + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "optional": true, + "engines": { + "node": ">=0.8" + } }, - "clone-response": { + "node_modules/clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, - "requires": { + "dependencies": { "mimic-response": "^1.0.0" } }, - "cls-bluebird": { + "node_modules/cls-bluebird": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", - "requires": { + "dependencies": { "is-bluebird": "^1.0.2", "shimmer": "^1.1.0" } }, - "co": { + "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "color-support": { + "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } }, - "combined-stream": { + "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { + "dependencies": { "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "optional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "configstore": { + "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", "dev": true, - "requires": { + "dependencies": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", "make-dir": "^3.0.0", "unique-string": "^2.0.0", "write-file-atomic": "^3.0.0", "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "console-control-strings": { + "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - }, - "core-util-is": { + "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "crypto-random-string": { + "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } + "engines": { + "node": ">=8" } }, - "dashdash": { + "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { + "dependencies": { "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" } }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { + "node_modules/debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { "ms": "^2.1.1" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", - "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress-response": { + "node_modules/decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, - "requires": { + "dependencies": { "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" } }, - "deep-extend": { + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=4.0.0" + } }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "optional": true, - "requires": { + "dependencies": { "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "defer-to-connect": { + "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", "dev": true }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { + "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } }, - "delegates": { + "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, - "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" - }, - "detect-libc": { + "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } }, - "detect-newline": { + "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", - "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "dot-prop": { + "node_modules/dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, - "requires": { + "dependencies": { "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "dotenv": { + "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } }, - "dottie": { + "node_modules/dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, - "duplexer3": { + "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", "stream-shift": "^1.0.0" } }, - "easy-table": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.1.tgz", - "integrity": "sha512-C9Lvm0WFcn2RgxbMnTbXZenMIWcBtkzMr+dWqq/JsVoGFSVUVlPqeOa5LP5kM0I3zoOazFpckOEb2/0LDFfToQ==", - "requires": { - "ansi-regex": "^3.0.0", - "wcwidth": ">=1.0.1" + "node_modules/easy-table": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", + "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "optionalDependencies": { + "wcwidth": "^1.0.1" } }, - "ecc-jsbn": { + "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { + "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, - "ecdsa-sig-formatter": { + "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { + "dependencies": { "safe-buffer": "^5.0.1" } }, - "emoji-regex": { + "node_modules/electron-to-chromium": { + "version": "1.4.601", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", + "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "end-of-stream": { + "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { + "dependencies": { "once": "^1.4.0" } }, - "error-ex": { + "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, - "requires": { + "dependencies": { "is-arrayish": "^0.2.1" } }, - "escape-goat": { + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "dev": true, + "engines": { + "node": ">=8" + } }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "engines": { + "node": ">=8" } }, - "esprima": { + "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "exit": { + "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "engines": { + "node": ">= 0.8.0" } }, - "expect": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.0.1.tgz", - "integrity": "sha512-QcCy4nygHeqmbw564YxNbHTJlXh47dVID2BUP52cZFpLU9zHViMFK6h07cC1wf7GYCTIigTdAXhVua8Yl1FkKg==", + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.0.0", - "jest-matcher-utils": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-regex-util": "^26.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { + "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] }, - "fast-deep-equal": { + "node_modules/fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, - "fast-json-stable-stringify": { + "node_modules/fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "requires": { + "dependencies": { "bser": "2.1.1" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "find-up": { + "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "requires": { + "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "follow-redirects": { + "node_modules/follow-redirects": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } }, - "forever-agent": { + "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } }, - "form-data": { + "node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { + "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" } }, - "format-util": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", - "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs-minipass": { + "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { + "dependencies": { "minipass": "^3.0.0" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { + "node_modules/fsevents": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "optional": true + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "gauge": { + "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "requires": { + "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", "console-control-strings": "^1.0.0", @@ -3786,141 +3476,120 @@ "strip-ansi": "^6.0.1", "wide-align": "^1.1.2" }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } + "engines": { + "node": ">=10" } }, - "generate-function": { + "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "requires": { + "dependencies": { "is-property": "^1.0.2" } }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "get-package-type": { + "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8.0.0" + } }, - "get-stream": { + "node_modules/get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, - "requires": { + "dependencies": { "pump": "^3.0.0" }, - "dependencies": { - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "engines": { + "node": ">=6" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { + "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { + "dependencies": { "assert-plus": "^1.0.0" } }, - "glob": { + "node_modules/glob": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "glob-parent": { + "node_modules/glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, - "requires": { + "dependencies": { "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "global-dirs": { + "node_modules/global-dirs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", "dev": true, - "requires": { + "dependencies": { "ini": "^1.3.5" + }, + "engines": { + "node": ">=8" } }, - "globals": { + "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "got": { + "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", "dev": true, - "requires": { + "dependencies": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", "cacheable-request": "^6.0.0", @@ -3932,2293 +3601,1362 @@ "p-cancelable": "^1.0.0", "to-readable-stream": "^1.0.0", "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "node_modules/hapi-swagger": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-17.2.0.tgz", + "integrity": "sha512-vcLz3OK7WLFsuY7cLgPJAulnuvkGSIE3XVbeD1XzoPXtb2jmuDUTg2yvrXx32EwlhSsyT/RP1MIVzHuc8KxvQw==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.1.0", + "@hapi/boom": "^10.0.1", + "@hapi/hoek": "^11.0.2", + "handlebars": "^4.7.8", + "http-status": "^1.7.3", + "swagger-parser": "^10.0.3", + "swagger-ui-dist": "^5.9.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@hapi/hapi": ">=20.x.x", + "joi": "17.x" + } }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "hapi-swagger": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-13.0.2.tgz", - "integrity": "sha512-fPUSVPLh1tpCS1q1ECUhmxL3sZCo6MmrQArwZIoh7QrwN7Tmw2zprz5nGS96KjSQE/MDJOfq82pdEq6FHD7yIQ==", - "requires": { - "@hapi/boom": "^8.0.1", - "@hapi/hoek": "^9.0.2", - "handlebars": "^4.5.3", - "http-status": "^1.0.1", - "json-schema-ref-parser": "^6.1.0", - "swagger-parser": "4.0.2", - "swagger-ui-dist": "^3.22.1" - }, - "dependencies": { - "@hapi/boom": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-8.0.1.tgz", - "integrity": "sha512-SnBM2GzEYEA6AGFKXBqNLWXR3uNBui0bkmklYXX1gYtevVhDTy2uakwkSauxvIWMtlANGRhzChYg95If3FWCwA==", - "requires": { - "@hapi/hoek": "8.x.x" - }, - "dependencies": { - "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" - } - } - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - } + "node_modules/hapi-swagger/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" } }, - "har-schema": { + "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } }, - "har-validator": { + "node_modules/har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { + "deprecated": "this library is no longer supported", + "dependencies": { "ajv": "^6.5.5", "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } }, - "has-unicode": { + "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "has-yarn": { + "node_modules/has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "html-escaper": { + "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-cache-semantics": { + "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, - "http-signature": { + "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { + "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" } }, - "http-status": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.4.2.tgz", - "integrity": "sha512-mBnIohUwRw9NyXMEMMv8/GANnzEYUj0Y8d3uL01zDWFkxUjYyZ6rgCaAI2zZ1Wb34Oqtbx/nFZolPRDc8Xlm5A==" + "node_modules/http-status": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.7.3.tgz", + "integrity": "sha512-GS8tL1qHT2nBCMJDYMHGkkkKQLNkIAHz37vgO68XKvzv+XyqB4oh/DfmMHdtRzfqSJPj1xKG2TaELZtlCz6BEQ==", + "engines": { + "node": ">= 0.4.0" + } }, - "https-proxy-agent": { + "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { + "dependencies": { "agent-base": "6", "debug": "4" }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } }, - "husky": { + "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" } }, - "ignore-by-default": { + "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, - "import-lazy": { + "node_modules/import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "requires": { + "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.19" + } }, - "inflection": { + "node_modules/inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", + "engines": [ + "node >= 0.4.0" + ] }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { + "node_modules/ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "engines": { + "node": "*" } }, - "is-arrayish": { + "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "requires": { + "dependencies": { "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-bluebird": { + "node_modules/is-bluebird": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=", + "engines": { + "node": ">=0.10.0" + } }, - "is-ci": { + "node_modules/is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, - "requires": { + "dependencies": { "ci-info": "^2.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "bin": { + "is-ci": "bin.js" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "is-generator-fn": { + "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-installed-globally": { + "node_modules/is-installed-globally": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", "dev": true, - "requires": { + "dependencies": { "global-dirs": "^2.0.1", "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "is-npm": { + "node_modules/is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "engines": { + "node": ">=0.12.0" } }, - "is-obj": { + "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-path-inside": { + "node_modules/is-path-inside": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "requires": { - "isobject": "^3.0.1" + "engines": { + "node": ">=8" } }, - "is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", - "dev": true - }, - "is-property": { + "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "is-typedarray": { + "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "is-yarn-global": { + "node_modules/is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "isstream": { + "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "requires": { - "@babel/core": "^7.7.5", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "engines": { + "node": ">=8" } }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "requires": { + "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "istanbul-lib-source-maps": { + "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "requires": { + "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, - "requires": { + "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "jest": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.0.1.tgz", - "integrity": "sha512-29Q54kn5Bm7ZGKIuH2JRmnKl85YRigp0o0asTc6Sb6l2ch1DCXIeZTLLFy9ultJvhkTqbswF5DEx4+RlkmCxWg==", + "node_modules/jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", "dev": true, - "requires": { - "@jest/core": "^26.0.1", + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", "import-local": "^3.0.2", - "jest-cli": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.0.1.tgz", - "integrity": "sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w==", - "dev": true, - "requires": { - "@jest/core": "^26.0.1", - "@jest/test-result": "^26.0.1", - "@jest/types": "^26.0.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.0.1", - "jest-util": "^26.0.1", - "jest-validate": "^26.0.1", - "prompts": "^2.0.1", - "yargs": "^15.3.1" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "jest-cli": "^28.1.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true } } }, - "jest-changed-files": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.0.1.tgz", - "integrity": "sha512-q8LP9Sint17HaE2LjxQXL+oYWW/WeeXMPE2+Op9X3mY8IEGFVc14xRxFjUuXUbcPAlDLhtWdIEt59GdQbn76Hw==", + "node_modules/jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "execa": "^4.0.0", - "throat": "^5.0.0" + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", - "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true } } }, - "jest-config": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.0.1.tgz", - "integrity": "sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg==", + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.0.1", - "@jest/types": "^26.0.1", - "babel-jest": "^26.0.1", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", "chalk": "^4.0.0", + "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.0.1", - "jest-environment-node": "^26.0.1", - "jest-get-type": "^26.0.0", - "jest-jasmine2": "^26.0.1", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.0.1", - "jest-util": "^26.0.1", - "jest-validate": "^26.0.1", - "micromatch": "^4.0.2", - "pretty-format": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } + "ts-node": { + "optional": true } } }, - "jest-diff": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.0.1.tgz", - "integrity": "sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ==", + "node_modules/jest-config/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.0.0", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" } + ], + "engines": { + "node": ">=8" } }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "node_modules/jest-config/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "requires": { - "detect-newline": "^3.0.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "jest-each": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.0.1.tgz", - "integrity": "sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q==", + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", + "dependencies": { "chalk": "^4.0.0", - "jest-get-type": "^26.0.0", - "jest-util": "^26.0.1", - "pretty-format": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-environment-jsdom": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz", - "integrity": "sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g==", + "node_modules/jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", "dev": true, - "requires": { - "@jest/environment": "^26.0.1", - "@jest/fake-timers": "^26.0.1", - "@jest/types": "^26.0.1", - "jest-mock": "^26.0.1", - "jest-util": "^26.0.1", - "jsdom": "^16.2.2" + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-environment-node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.0.1.tgz", - "integrity": "sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ==", + "node_modules/jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", "dev": true, - "requires": { - "@jest/environment": "^26.0.1", - "@jest/fake-timers": "^26.0.1", - "@jest/types": "^26.0.1", - "jest-mock": "^26.0.1", - "jest-util": "^26.0.1" + "dependencies": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-get-type": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", - "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", - "dev": true + "node_modules/jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } }, - "jest-haste-map": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.0.1.tgz", - "integrity": "sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA==", + "node_modules/jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "@types/graceful-fs": "^4.1.2", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-serializer": "^26.0.0", - "jest-util": "^26.0.1", - "jest-worker": "^26.0.0", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7", - "which": "^2.0.2" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "jest-jasmine2": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz", - "integrity": "sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg==", + "node_modules/jest-haste-map/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.0.1", - "@jest/source-map": "^26.0.0", - "@jest/test-result": "^26.0.1", - "@jest/types": "^26.0.1", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.0.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.0.1", - "jest-matcher-utils": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-runtime": "^26.0.1", - "jest-snapshot": "^26.0.1", - "jest-util": "^26.0.1", - "pretty-format": "^26.0.1", - "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "jest-leak-detector": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.0.1.tgz", - "integrity": "sha512-93FR8tJhaYIWrWsbmVN1pQ9ZNlbgRpfvrnw5LmgLRX0ckOJ8ut/I35CL7awi2ecq6Ca4lL59bEK9hr7nqoHWPA==", + "node_modules/jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", "dev": true, - "requires": { - "jest-get-type": "^26.0.0", - "pretty-format": "^26.0.1" + "dependencies": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-matcher-utils": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz", - "integrity": "sha512-PUMlsLth0Azen8Q2WFTwnSkGh2JZ8FYuwijC8NR47vXKpsrKmA1wWvgcj1CquuVfcYiDEdj985u5Wmg7COEARw==", + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", "dev": true, - "requires": { + "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^26.0.1", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-message-util": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.0.1.tgz", - "integrity": "sha512-CbK8uQREZ8umUfo8+zgIfEt+W7HAHjQCoRaNs4WxKGhAYBGwEyvxuK81FXa7VeB9pwDEXeeKOB2qcsNVCAvB7Q==", + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.0.1", - "@types/stack-utils": "^1.0.1", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-mock": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.0.1.tgz", - "integrity": "sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q==", + "node_modules/jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1" + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", - "dev": true + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true + "node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } }, - "jest-resolve": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.0.1.tgz", - "integrity": "sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ==", + "node_modules/jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", + "dependencies": { "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.1", - "jest-util": "^26.0.1", - "read-pkg-up": "^7.0.1", - "resolve": "^1.17.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", "slash": "^3.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-resolve-dependencies": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.0.1.tgz", - "integrity": "sha512-9d5/RS/ft0vB/qy7jct/qAhzJsr6fRQJyGAFigK3XD4hf9kIbEH5gks4t4Z7kyMRhowU6HWm/o8ILqhaHdSqLw==", + "node_modules/jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.0.1" + "dependencies": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-runner": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.0.1.tgz", - "integrity": "sha512-CApm0g81b49Znm4cZekYQK67zY7kkB4umOlI2Dx5CwKAzdgw75EN+ozBHRvxBzwo1ZLYZ07TFxkaPm+1t4d8jA==", + "node_modules/jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", "dev": true, - "requires": { - "@jest/console": "^26.0.1", - "@jest/environment": "^26.0.1", - "@jest/test-result": "^26.0.1", - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.0.1", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.0.1", - "jest-jasmine2": "^26.0.1", - "jest-leak-detector": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-resolve": "^26.0.1", - "jest-runtime": "^26.0.1", - "jest-util": "^26.0.1", - "jest-worker": "^26.0.0", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.0.1.tgz", - "integrity": "sha512-Ci2QhYFmANg5qaXWf78T2Pfo6GtmIBn2rRaLnklRyEucmPccmCKvS9JPljcmtVamsdMmkyNkVFb9pBTD6si9Lw==", - "dev": true, - "requires": { - "@jest/console": "^26.0.1", - "@jest/environment": "^26.0.1", - "@jest/fake-timers": "^26.0.1", - "@jest/globals": "^26.0.1", - "@jest/source-map": "^26.0.0", - "@jest/test-result": "^26.0.1", - "@jest/transform": "^26.0.1", - "@jest/types": "^26.0.1", - "@types/yargs": "^15.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", + "execa": "^5.0.0", "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.0.1", - "jest-haste-map": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-mock": "^26.0.1", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.0.1", - "jest-snapshot": "^26.0.1", - "jest-util": "^26.0.1", - "jest-validate": "^26.0.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-serializer": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.0.0.tgz", - "integrity": "sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ==", + "node_modules/jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", "dev": true, - "requires": { - "graceful-fs": "^4.2.4" + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "jest-snapshot": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.0.1.tgz", - "integrity": "sha512-jxd+cF7+LL+a80qh6TAnTLUZHyQoWwEHSUFJjkw35u3Gx+BZUNuXhYvDqHXr62UQPnWo2P6fvQlLjsU93UKyxA==", + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.0.1", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.0.1", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.0.1", - "jest-get-type": "^26.0.0", - "jest-matcher-utils": "^26.0.1", - "jest-message-util": "^26.0.1", - "jest-resolve": "^26.0.1", - "make-dir": "^3.0.0", - "natural-compare": "^1.4.0", - "pretty-format": "^26.0.1", - "semver": "^7.3.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "jest-util": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.0.1.tgz", - "integrity": "sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g==", + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "make-dir": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" } + ], + "engines": { + "node": ">=8" } }, - "jest-validate": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.0.1.tgz", - "integrity": "sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA==", + "node_modules/jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "camelcase": "^6.0.0", + "dependencies": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^26.0.0", + "jest-get-type": "^28.0.2", "leven": "^3.1.0", - "pretty-format": "^26.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "jest-watcher": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.0.1.tgz", - "integrity": "sha512-pdZPydsS8475f89kGswaNsN3rhP6lnC3/QDCppP7bg1L9JQz7oU9Mb/5xPETk1RHDCWeqmVC47M4K5RR7ejxFw==", + "node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", "dev": true, - "requires": { - "@jest/test-result": "^26.0.1", - "@jest/types": "^26.0.1", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^26.0.1", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", "string-length": "^4.0.1" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "jest-worker": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz", - "integrity": "sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw==", + "node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", "dev": true, - "requires": { + "dependencies": { + "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "joi": { + "node_modules/joi": { "version": "17.10.1", "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.1.tgz", "integrity": "sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw==", - "requires": { + "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0", "@sideway/address": "^4.1.3", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" - }, - "dependencies": { - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - } } }, - "js-tokens": { + "node_modules/joi/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "jsbn": { + "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "jsdom": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz", - "integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "acorn": "^7.1.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.2.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", - "domexception": "^2.0.1", - "escodegen": "^1.14.1", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "5.1.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.0.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.2.3", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - } - } - }, - "jsesc": { + "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } }, - "json-buffer": { + "node_modules/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "json-schema": { + "node_modules/json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, - "json-schema-ref-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", - "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", - "requires": { - "call-me-maybe": "^1.0.1", - "js-yaml": "^3.12.1", - "ono": "^4.0.11" - } - }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stringify-safe": { + "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "requires": { - "minimist": "^1.2.5" + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, - "jsonwebtoken": { + "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { + "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", @@ -6229,440 +4967,361 @@ "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" } }, - "jsprim": { + "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { + "engines": [ + "node >=0.6.0" + ], + "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" } }, - "jwa": { + "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { + "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, - "jws": { + "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { + "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, - "kareem": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", - "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" - }, - "keyv": { + "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", "dev": true, - "requires": { + "dependencies": { "json-buffer": "3.0.0" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "kleur": { + "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "latest-version": { + "node_modules/latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", "dev": true, - "requires": { + "dependencies": { "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" } }, - "leven": { + "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "engines": { + "node": ">=6" } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "locate-path": { + "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "requires": { + "dependencies": { "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash.get": { + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, - "lodash.includes": { + "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, - "lodash.isboolean": { + "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, - "lodash.isequal": { + "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, - "lodash.isinteger": { + "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" }, - "lodash.isnumber": { + "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" }, - "lodash.isplainobject": { + "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" }, - "lodash.isstring": { + "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, - "lodash.once": { + "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "long": { + "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "lowercase-keys": { + "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "lru-cache": { + "node_modules/lru-cache": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } }, - "make-dir": { + "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { + "dependencies": { "semver": "^6.0.0" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "requires": { - "object-visit": "^1.0.0" + "dependencies": { + "tmpl": "1.0.5" } }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "merge-stream": { + "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime-db": { + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { + "node_modules/mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { + "dependencies": { "mime-db": "1.40.0" }, - "dependencies": { - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - } + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "engines": { + "node": ">= 0.6" } }, - "mimic-fn": { + "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "mimic-response": { + "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, - "minipass": { + "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } }, - "minizlib": { + "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { + "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } + "engines": { + "node": ">= 8" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "mkdirp": { + "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } }, - "moment": { + "node_modules/moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "engines": { + "node": "*" + } }, - "moment-timezone": { + "node_modules/moment-timezone": { "version": "0.5.31", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "requires": { + "dependencies": { "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" } }, - "mongodb": { - "version": "3.5.7", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.7.tgz", - "integrity": "sha512-lMtleRT+vIgY/JhhTn1nyGwnSMmJkJELp+4ZbrjctrnBxuLbj6rmLuJFz8W2xUzUqWmqoyVxJLYuC58ZKpcTYQ==", - "requires": { - "bl": "^2.2.0", - "bson": "^1.1.4", - "denque": "^1.4.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - }, - "mongoose": { - "version": "5.9.16", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.16.tgz", - "integrity": "sha512-b4HNndgh+dacoLE/2SBF3iBBofeaKL+aGVZH7jnPRc2RXRCplX4sfH5sgoz03ryCSXJ+RQNIfqKAADt/ZBzPDA==", - "requires": { - "bson": "^1.1.4", - "kareem": "2.3.1", - "mongodb": "3.5.7", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.7.0", - "mquery": "3.2.2", - "ms": "2.1.2", - "regexp-clone": "1.0.0", - "safe-buffer": "5.1.2", - "sift": "7.0.1", - "sliced": "1.0.1" - } - }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" - }, - "mpath": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", - "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" - }, - "mquery": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", - "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", - "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "dependencies": { - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "mysql2": { + "node_modules/mysql2": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", - "requires": { + "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", @@ -6672,166 +5331,121 @@ "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "named-placeholders": { + "node_modules/named-placeholders": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", - "requires": { + "dependencies": { "lru-cache": "^7.14.1" }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } + "engines": { + "node": ">=12.0.0" } }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" } }, - "natural-compare": { + "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, - "node-addon-api": { + "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, - "node-fetch": { + "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { + "dependencies": { "whatwg-url": "^5.0.0" }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true } } }, - "node-int64": { + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, - "node-notifier": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-7.0.1.tgz", - "integrity": "sha512-VkzhierE7DBmQEElhTGJIoiZa1oqRijOtgOlsXg32KrJRXsPy0NXFBqWGW/wTswnJlDCs5viRYaqWguqzsKcmg==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.1.1", - "semver": "^7.2.1", - "shellwords": "^0.1.1", - "uuid": "^7.0.3", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true, - "optional": true - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true, - "optional": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "nodemon": { + "node_modules/nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", "dev": true, - "requires": { + "hasInstallScript": true, + "dependencies": { "chokidar": "^3.2.2", "debug": "^3.2.6", "ignore-by-default": "^1.0.1", @@ -6842,574 +5456,491 @@ "touch": "^3.1.0", "undefsafe": "^2.0.2", "update-notifier": "^4.0.0" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "nopt": { + "node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, - "requires": { + "dependencies": { "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" + "engines": { + "node": ">=0.10.0" } }, - "normalize-url": { + "node_modules/normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "requires": { - "path-key": "^2.0.0" + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "npmlog": { + "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "requires": { + "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", "gauge": "^3.0.0", "set-blocking": "^2.0.0" } }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "oauth-sign": { + "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } }, - "object-assign": { + "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { + "dependencies": { "wrappy": "1" } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "requires": { + "dependencies": { "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "ono": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", - "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", - "requires": { - "format-util": "^1.0.3" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true }, - "p-cancelable": { + "node_modules/p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "p-each-series": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", - "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { - "p-try": "^2.0.0" + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { + "node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "requires": { + "dependencies": { "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-try": { + "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "package-json": { + "node_modules/package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", "dev": true, - "requires": { + "dependencies": { "got": "^9.6.0", "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "engines": { + "node": ">=8" } }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "requires": { + "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "performance-now": { + "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" + "engines": { + "node": ">= 6" } }, - "pkg-dir": { + "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, - "requires": { + "dependencies": { "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { + "node_modules/prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } }, - "pretty-format": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", - "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "requires": { - "@jest/types": "^26.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" + "engines": { + "node": ">=10" }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "prompts": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", - "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "requires": { + "dependencies": { "kleur": "^3.0.3", - "sisteransi": "^1.0.4" + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, - "proxy-from-env": { + "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "psl": { + "node_modules/psl": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" }, - "pstree.remy": { + "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "requires": { - "duplexify": "^3.6.0", + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dependencies": { + "duplexify": "^4.1.1", "inherits": "^2.0.3", - "pump": "^2.0.0" + "pump": "^3.0.0" } }, - "punycode": { + "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } }, - "pupa": { + "node_modules/pupa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", "dev": true, - "requires": { + "dependencies": { "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "qs": { + "node_modules/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } }, - "rc": { + "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, - "requires": { + "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "bin": { + "rc": "cli.js" } }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "readdirp": { + "node_modules/readdirp": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "dev": true, - "requires": { + "dependencies": { "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "registry-auth-token": { + "node_modules/registry-auth-token": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", "dev": true, - "requires": { + "dependencies": { "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" } }, - "registry-url": { + "node_modules/registry-url": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", "dev": true, - "requires": { + "dependencies": { "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" } }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "request": { + "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", @@ -7431,230 +5962,158 @@ "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "dev": true, - "requires": { - "lodash": "^4.17.15" + "engines": { + "node": ">= 6" } }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" } }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - }, - "dependencies": { - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - } + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" } }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "requires": { - "path-parse": "^1.0.6" + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "resolve-cwd": { + "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "requires": { + "dependencies": { "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "resolve-from": { + "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "responselike": { + "node_modules/responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", "dev": true, - "requires": { + "dependencies": { "lowercase-keys": "^1.0.0" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry-as-promised": { + "node_modules/retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "requires": { + "dependencies": { "any-promise": "^1.3.0" } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - } - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { + "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } }, - "semver-diff": { + "node_modules/semver-diff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", "dev": true, - "requires": { + "dependencies": { "semver": "^6.3.0" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "engines": { + "node": ">=8" } }, - "seq-queue": { + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, - "sequelize": { + "node_modules/sequelize": { "version": "5.21.11", "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.11.tgz", "integrity": "sha512-ZJw3Hp+NS7iHcTz4fHlKvIBm4I7xYibYRCP4HhSyMB26xgqFYFOXTaeWbHD2UUwAFaksTLw5ntzfpA9kE33SVA==", - "requires": { + "deprecated": "Please update to v6 or higher! A migration guide can be found here: https://sequelize.org/v6/manual/upgrade-to-v6.html", + "dependencies": { "bluebird": "^3.5.0", "cls-bluebird": "^2.1.0", "debug": "^4.1.1", @@ -7671,347 +6130,134 @@ "validator": "^10.11.0", "wkx": "^0.4.8" }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } + "engines": { + "node": ">=6.0.0" } }, - "sequelize-mock": { + "node_modules/sequelize-mock": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", "integrity": "sha512-Vu95by/Bmhcx9PHKlZe+w7/7zw1AycV/SeevxQ5lDokAb50H7Kaf2SkjK5mqKxHWX6y/ICZ8JEfyMOg0nd1M2w==", "dev": true, - "requires": { + "dependencies": { "bluebird": "^3.4.6", "inflection": "^1.10.0", "lodash": "^4.16.4" } }, - "sequelize-pool": { + "node_modules/sequelize-pool": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", - "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/sequelize/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/sequelize/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } }, - "set-blocking": { + "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { - "shebang-regex": "^1.0.0" + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "optional": true + "engines": { + "node": ">=8" + } }, - "shimmer": { + "node_modules/shimmer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, - "sift": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", - "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "sisteransi": { + "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, - "slash": { + "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "engines": { + "node": ">=8" } }, - "source-map": { + "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" } }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "requires": { + "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, - "sqlstring": { + "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } }, - "sshpk": { + "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { + "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", @@ -8021,505 +6267,430 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" } }, - "stack-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", - "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "requires": { + "dependencies": { "escape-string-regexp": "^2.0.0" }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } + "engines": { + "node": ">=10" } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "stream-shift": { + "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, - "string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "requires": { + "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" } }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - } + "engines": { + "node": ">=8" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - }, + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - } + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-bom": { + "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "strip-final-newline": { + "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { + "dev": true, + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": ">=8" } }, - "swagger-methods": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/swagger-methods/-/swagger-methods-1.0.8.tgz", - "integrity": "sha512-G6baCwuHA+C5jf4FNOrosE4XlmGsdjbOjdBK4yuiDDj/ro9uR4Srj3OR84oQMT8F3qKp00tYNv0YN730oTHPZA==" + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "swagger-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-4.0.2.tgz", - "integrity": "sha512-hKslog8LhsXICJ1sMLsA8b8hQ3oUEX0457aLCFJc4zz6m8drmnCtyjbVqS5HycaKFOKVolJc2wFoe8KDPWfp4g==", - "requires": { - "call-me-maybe": "^1.0.1", - "debug": "^3.1.0", - "json-schema-ref-parser": "^4.1.0", - "ono": "^4.0.3", - "swagger-methods": "^1.0.4", - "swagger-schema-official": "2.0.0-bab6bed", - "z-schema": "^3.19.0" - }, - "dependencies": { - "json-schema-ref-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-4.1.1.tgz", - "integrity": "sha512-lByoCHZ6H2zgb6NtsXIqtzQ+6Ji7iVqnrhWxsXLhF+gXmgu6E8+ErpDxCMR439MUG1nfMjWI2HAoM8l0XgSNhw==", - "requires": { - "call-me-maybe": "^1.0.1", - "debug": "^3.1.0", - "js-yaml": "^3.10.0", - "ono": "^4.0.3" - } - } + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "swagger-schema-official": { - "version": "2.0.0-bab6bed", - "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", - "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=" + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "swagger-ui-dist": { - "version": "3.25.5", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.5.tgz", - "integrity": "sha512-JZ6dVQS2nPgVYW0+JIIxm84vvFbR/ole6xYJG2DcSdejDLt8ARqIhbZ4InL7RVsLdXpPirUMb7hf2z4Fzqesyw==" + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "node_modules/swagger-ui-dist": { + "version": "5.10.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.10.3.tgz", + "integrity": "sha512-fu3aozjxFWsmcO1vyt1q1Ji2kN7KlTd1vHy27E9WgPyXo9nrEzhQPqgxaAjbMsOmb8XFKNGo4Sa3Q+84Fh+pFw==" }, - "tar": { + "node_modules/tar": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", - "requires": { + "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "term-size": { + "node_modules/term-size": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "terminal-link": { + "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", "dev": true, - "requires": { + "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "test-exclude": { + "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "requires": { + "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" } }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "to-fast-properties": { + "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "engines": { + "node": ">=4" } }, - "to-readable-stream": { + "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "engines": { + "node": ">=6" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "toposort-class": { + "node_modules/toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" }, - "touch": { + "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, - "requires": { + "dependencies": { "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "tunnel-agent": { + "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "tweetnacl": { + "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { + "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "type-fest": { + "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "typedarray-to-buffer": { + "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, - "requires": { + "dependencies": { "is-typedarray": "^1.0.0" } }, - "uglify-js": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.3.tgz", - "integrity": "sha512-KfQUgOqTkLp2aZxrMbCuKCDGW9slFYu2A23A36Gs7sGzTLcRBDORdOi5E21KWHFIfkY8kzgi/Pr1cXCh0yIp5g==", + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "optional": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" } }, - "undefsafe": { + "node_modules/undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", "dev": true, - "requires": { + "dependencies": { "debug": "^2.2.0" - }, + } + }, + "node_modules/undefsafe/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "ms": "2.0.0" } }, - "underscore": { + "node_modules/undefsafe/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unique-string": { + "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, - "requires": { + "dependencies": { "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "update-notifier": { + "node_modules/update-notifier": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", "dev": true, - "requires": { + "dependencies": { "boxen": "^4.2.0", "chalk": "^3.0.0", "configstore": "^5.0.1", @@ -8534,393 +6705,309 @@ "semver-diff": "^3.1.1", "xdg-basedir": "^4.0.0" }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { + "dependencies": { "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url-parse-lax": { + "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", "dev": true, - "requires": { + "dependencies": { "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "uuid": { + "node_modules/uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } }, - "v8-to-istanbul": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", - "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, - "requires": { + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^2.0.0" }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } + "engines": { + "node": ">=10.12.0" } }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true }, - "validator": { + "node_modules/validator": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "engines": { + "node": ">= 0.10" + } }, - "verror": { + "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { + "engines": [ + "node >=0.6.0" + ], + "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "requires": { - "makeerror": "1.0.x" + "dependencies": { + "makeerror": "1.0.12" } }, - "wcwidth": { + "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "optional": true, - "requires": { + "dependencies": { "defaults": "^1.0.3" } }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz", - "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^5.0.0" - }, "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { + "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { + "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "widest-line": { + "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, - "requires": { + "dependencies": { "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "wkx": { + "node_modules/wkx": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", - "requires": { + "dependencies": { "@types/node": "*" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write-file-atomic": { + "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, - "requires": { + "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", - "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", - "dev": true - }, - "xdg-basedir": { + "node_modules/xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "z-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.25.1.tgz", - "integrity": "sha512-7tDlwhrBG+oYFdXNOjILSurpfQyuVgkRe3hB2q8TEssamDHB7BbLWYkYO98nTn0FibfdFroFKDjndbgufAgS/Q==", - "requires": { - "commander": "^2.7.1", - "core-js": "^2.5.7", - "lodash.get": "^4.0.0", - "lodash.isequal": "^4.0.0", - "validator": "^10.0.0" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" } } } diff --git a/package.json b/package.json index 5606170..1d74cc8 100644 --- a/package.json +++ b/package.json @@ -13,21 +13,22 @@ }, "dependencies": { "@hapi/boom": "^9.1.0", - "@hapi/good": "^9.0.0", - "@hapi/good-console": "^9.0.0", + "@hapi/good": "^9.0.1", + "@hapi/good-console": "^9.0.1", "@hapi/good-squeeze": "^6.0.0", - "@hapi/hapi": "^20.0.0", - "@hapi/inert": "^6.0.1", + "@hapi/hapi": "^21.3.2", + "@hapi/hoek": "^11.0.2", + "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", - "@hapi/vision": "^6.0.0", + "@hapi/shot": "^6.0.1", + "@hapi/vision": "^7.0.3", "axios": "^1.5.1", "bcrypt": "^5.1.1", - "blipp": "^4.0.1", + "blipp": "^4.0.2", "dotenv": "^8.6.0", - "hapi-swagger": "^13.0.2", + "hapi-swagger": "^17.2.0", "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", - "mongoose": "^5.9.16", "mysql2": "^3.6.0", "request": "^2.88.2", "require-directory": "^2.1.1", @@ -36,7 +37,7 @@ }, "devDependencies": { "husky": "^8.0.3", - "jest": "^26.0.1", + "jest": "^28.0.0", "nodemon": "^2.0.4", "sequelize-mock": "^0.10.2" }, From 69a0de7c7cabc9e7daa847a1ca0517d85e8f7b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:20:31 +0100 Subject: [PATCH 05/67] test correction --- test/integration/users.test.js | 29 ------------------- .../repositories/DocumentRepository.test.js | 5 +--- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 436e9a1..da2a14a 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -334,33 +334,4 @@ describe('user route', () => { expect(res.statusCode).toBe(400); }) }) - describe("/users/uploadPreviewProfile", () =>{ - it("should respond code 200", async () =>{ - const form = new FormData(); - server.app.serviceLocator.accessTokenManager.decode = jest.fn((test) => ({ value: 1 })); - const currentDirectory = process.cwd(); - const imageBuffer = fs.createReadStream(currentDirectory+'\\test\\integration\\fixture\\imageTest.jpg'); - await form.append('file', imageBuffer); - console.log(form.getHeaders()) - - const res = server.inject({ - method: 'POST', - url: '/users/uploadPreviewProfile', - payload: form, - headers: { - Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDEwMDgwNzV9.-n_G0pKIBI9jkUD56kYjby03tFnqYLttc_Z51eLvhKU', - ...form.getHeaders() - } , - }) - console.log(res) - res.then(null,(error) => console.log(error)) - function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - console.log('Hello'); - await sleep(20000); - expect(res.statusCode).toBe(401) - }) - }) }); diff --git a/test/unit/infrastructure/repositories/DocumentRepository.test.js b/test/unit/infrastructure/repositories/DocumentRepository.test.js index 4f25fed..f2cff32 100644 --- a/test/unit/infrastructure/repositories/DocumentRepository.test.js +++ b/test/unit/infrastructure/repositories/DocumentRepository.test.js @@ -19,10 +19,7 @@ describe('document repository', ()=>{ }) describe('document repository with errors', () =>{ it("shouldn't create file", async () =>{ - await expect(documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder",{})).rejects.toThrow('Cannot read property \'filename\' of undefined') - }) - it("shouldn't delete file", async () =>{ - await expect(documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder/test.png",{})).rejects.toThrow('Cannot read property \'filename\' of undefined') + await expect(documentRepository.uploadFile("test/unit/infrastructure/repositories/testFolder",{})).rejects.toThrow() }) }) }) \ No newline at end of file From 361b7ff092ccfcb412911cf20ad8da27202f6a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:13:36 +0100 Subject: [PATCH 06/67] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1d74cc8..120000c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@hapi/hoek": "^11.0.2", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", + "@hapi/inert": "^7.1.0", "@hapi/shot": "^6.0.1", "@hapi/vision": "^7.0.3", "axios": "^1.5.1", From 393eaf6a69047bd18bb417d5b43b1e61027d19b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:54:34 +0100 Subject: [PATCH 07/67] Feature/generate auth request (#14) * save * finished auth * removed useless method * Update SpotifyRepositoryAbstract.js --- .gitignore | 1 - .../use_cases/spotify/GetAuthURL.js | 25 ++++++++++ .../interfaces/SpotifyRepositoryAbstract.js | 1 + .../controllers/SpotifyController.js | 13 +++++ lib/interfaces/routes/spotify.js | 23 +++++++++ package-lock.json | 47 +++++++++++++++++++ package.json | 1 + 7 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 lib/application/use_cases/spotify/GetAuthURL.js diff --git a/.gitignore b/.gitignore index cad71b0..8bec7db 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ node_modules upload -.env diff --git a/lib/application/use_cases/spotify/GetAuthURL.js b/lib/application/use_cases/spotify/GetAuthURL.js new file mode 100644 index 0000000..d546ee1 --- /dev/null +++ b/lib/application/use_cases/spotify/GetAuthURL.js @@ -0,0 +1,25 @@ +const spotifyScopes = [ + 'user-read-private', + 'user-read-email', + 'user-library-read', + 'playlist-read-private', + 'playlist-read-collaborative', + 'playlist-modify-public', + 'playlist-modify-private', + 'user-read-playback-state', + 'user-modify-playback-state', + 'user-read-currently-playing', + 'user-follow-read', + 'user-follow-modify', + 'user-library-read', + 'user-library-modify', + 'user-top-read', + 'user-read-recently-played', + 'ugc-image-upload' +]; +const crypto = require("crypto"); + +module.exports = async ({spotifyRepository}) => { + const state = crypto.randomBytes(16).toString('hex') + return encodeURI(`https://accounts.spotify.com/authorize?client_id=${process.env.CLIENT_ID}&response_type=${'code'}&redirect_uri=${encodeURI(process.env.SPOTIFY_AUTH_REDIRECT)}&show_dialog=${'true'}&scope=${spotifyScopes.join(' ')}`) +} \ No newline at end of file diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 12059c5..6a77f3f 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -13,4 +13,5 @@ module.exports = class { + }; diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index b5d08fd..bf67a17 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -1,4 +1,5 @@ const search = require("../../application/use_cases/spotify/Search"); +const getAuthURL = require("../../application/use_cases/spotify/GetAuthURL"); const handleError = require("./utils/handleError"); module.exports = { @@ -14,5 +15,17 @@ module.exports = { }catch(error){ return handleError(error) } + }, + async getAuthURL(request,handler) { + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; + // Input + + const authURL = await getAuthURL(serviceLocator) + return handler.response(authURL).code(200) + }catch(error){ + return handleError(error) + } } } \ No newline at end of file diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index ea35393..886ff5b 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -31,6 +31,29 @@ module.exports = { query: trackBody } }, + }, + { + method: 'GET', + path: '/spotify/getAuthURL', + handler: spotify.getAuthURL, + options: { + description: 'get auth URL', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + }, } ]); } diff --git a/package-lock.json b/package-lock.json index 13b8512..37ccdd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@hapi/good-squeeze": "^6.0.0", "@hapi/hapi": "^21.3.2", "@hapi/hoek": "^11.0.2", + "@hapi/inert": "^7.1.0", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", "@hapi/shot": "^6.0.1", @@ -1136,6 +1137,52 @@ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" }, + "node_modules/@hapi/inert": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@hapi/inert/-/inert-7.1.0.tgz", + "integrity": "sha512-5X+cl/Ozm0U9uPGGX1dSKhnhTQIf161bH/kkTN9OBVAZKFG+nrj8j/NMj6S1zBBZWmQrkVRNPfCUGrXzB4fCFQ==", + "dependencies": { + "@hapi/ammo": "^6.0.1", + "@hapi/boom": "^10.0.1", + "@hapi/bounce": "^3.0.1", + "@hapi/hoek": "^11.0.2", + "@hapi/validate": "^2.0.1", + "lru-cache": "^7.14.1" + } + }, + "node_modules/@hapi/inert/node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/inert/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/inert/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", + "dependencies": { + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" + } + }, + "node_modules/@hapi/inert/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/@hapi/iron": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz", diff --git a/package.json b/package.json index 120000c..58f0752 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@hapi/good-squeeze": "^6.0.0", "@hapi/hapi": "^21.3.2", "@hapi/hoek": "^11.0.2", + "@hapi/inert": "^7.1.0", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", "@hapi/inert": "^7.1.0", From 0403c7ecb92e8489f509ae60fe7e7c3069ad9103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 19 Dec 2023 22:52:02 +0100 Subject: [PATCH 08/67] features/getToken --- lib/application/use_cases/spotify/GetToken.js | 10 ++++ .../use_cases/spotify/RefreshToken.js | 10 ++++ lib/application/use_cases/user/CreateUser.js | 5 +- lib/domain/entity/UserEntity.js | 10 ++-- lib/domain/model/User.js | 5 +- lib/infrastructure/config/service-locator.js | 2 +- .../orm/sequelize/models/Utilisateur.js | 8 +++- .../repositories/SpotifyRepository.js | 43 ++++++++++++++++- .../repositories/UserRepository.js | 47 +++++++++++++++---- .../interfaces/SpotifyRepositoryAbstract.js | 4 +- .../interfaces/UserRepositoryAbstract.js | 6 +++ lib/infrastructure/routine/refreshTokens.js | 7 +++ lib/infrastructure/webserver/server.js | 3 +- lib/interfaces/controllers/UsersController.js | 13 ++++- test/integration/users.test.js | 21 +++++++-- .../usecase/fixtures/searchFixture.js | 1 + .../usecase/spotify/GetToken.test.js | 28 +++++++++++ .../application/usecase/user/user.test.js | 7 ++- 18 files changed, 199 insertions(+), 31 deletions(-) create mode 100644 lib/application/use_cases/spotify/GetToken.js create mode 100644 lib/application/use_cases/spotify/RefreshToken.js create mode 100644 lib/infrastructure/routine/refreshTokens.js create mode 100644 test/unit/application/usecase/spotify/GetToken.test.js diff --git a/lib/application/use_cases/spotify/GetToken.js b/lib/application/use_cases/spotify/GetToken.js new file mode 100644 index 0000000..57962f7 --- /dev/null +++ b/lib/application/use_cases/spotify/GetToken.js @@ -0,0 +1,10 @@ +const throwStatusCode = require("../utils/throwStatusCode") +module.exports = async (code,{spotifyRepository}) => { + const {access_token, refresh_token,error} = await spotifyRepository.getToken(code) + if(error) + throwStatusCode(400,error) + return { + token:access_token, + refresh_token + } +} \ No newline at end of file diff --git a/lib/application/use_cases/spotify/RefreshToken.js b/lib/application/use_cases/spotify/RefreshToken.js new file mode 100644 index 0000000..72be8b4 --- /dev/null +++ b/lib/application/use_cases/spotify/RefreshToken.js @@ -0,0 +1,10 @@ +module.exports = async (id,{spotifyRepository,userRepository}) => { + setInterval(async ()=>{ + const user = await userRepository.getByUser(id) + console.log(user) + + const {access_token} = await spotifyRepository.refreshToken(user.refresh_token) + user.token = access_token + userRepository.updateUser(user) + },3500*1000) +} \ No newline at end of file diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index d10dba7..6ee7fac 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -4,7 +4,7 @@ const User = require('../../../domain/model/User'); const bcrypt = require("bcrypt"); const rolesEnum = require('../../../domain/model/utils/RolesEnum') const throwStatusCode = require("../utils/throwStatusCode") -module.exports = async (pseudo, email,alias, bio, password,spotifyToken, { userRepository }) => { +module.exports = async (pseudo, email,alias, bio, password,token,refreshToken, { userRepository }) => { password = await bcrypt.hash(password,10) if(!password) { throwStatusCode('500','Internal server error') @@ -22,7 +22,8 @@ module.exports = async (pseudo, email,alias, bio, password,spotifyToken, { userR null, null, password, - spotifyToken, + token, + refreshToken, rolesEnum.UTILISATEUR, null); return userRepository.persist(user) diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index 56746ae..9d7714b 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -6,16 +6,16 @@ const userSignIn = Joi.object().keys({ }) const userSignUp = Joi.object().keys({ - email: Joi.string().email().min(10).max(40).error(validationErrror("email","l'email est invalide")), - pseudo: Joi.string().min(3).max(15).custom((value, helpers) => { + email: Joi.string().email().min(10).max(40).required().error(validationErrror("email","l'email est invalide")), + pseudo: Joi.string().min(3).max(15).required().custom((value, helpers) => { if (value.includes('@')) { return helpers.error('any.invalid'); } return value; }, 'pseudo validation').error(validationErrror("pseudo","le pseudo doit être compris entre 3 et 15 caractère")), - alias: Joi.string().min(3).max(15).error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), - password: Joi.string().min(8).max(30).error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")), - spotifyToken: Joi.string().max(40), + alias: Joi.string().min(3).max(15).required().error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), + password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")), + spotifyToken: Joi.string().max(1000), bio:Joi.string().max(1500).error(validationErrror("bio","le pseudo doit faire moins de 1500 caractères")), }) diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index 1293a12..9191dfd 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -2,14 +2,15 @@ module.exports = class { - constructor(id = null, pseudo, email,alias, bio, photo,tempPhoto, password,spotifyToken,id_role, banUntil) { + constructor(id = null, pseudo, email,alias, bio, photo,tempPhoto, password,token,refresh_token,id_role, banUntil) { this.id = id; this.pseudo = pseudo; this.email = email; this.alias = alias this.photo = photo this.tempPhoto = tempPhoto - this.spotifyToken = spotifyToken + this.token = token + this.refresh_token = refresh_token this.password = password; this.password = password; this.id_role = id_role; diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index 01c17b7..f75bf4f 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -11,7 +11,7 @@ function buildBeans() { return { accessTokenManager: new JwtAccessTokenManager(), userRepository: new UserRepository(), - spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET), + spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET,process.env.SPOTIFY_AUTH_REDIRECT), documentRepository: new documentRepository() }; } diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index 22ff063..0dbfca5 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -28,10 +28,14 @@ module.exports = (sequelize) => { unique: true, allowNull: false, }, - token_spotify: { - type: DataTypes.STRING(50), + token: { + type: DataTypes.STRING(400), unique: true, }, + refresh_token: { + type: DataTypes.STRING(400), + unique: true, + }, id_spotify: { type: DataTypes.STRING(50), }, diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index 5789ba4..ee7b1ca 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -4,10 +4,11 @@ const axios = require("axios"); const spotifyRepositoryAbstract = require('./interfaces/SpotifyRepositoryAbstract') module.exports = class extends spotifyRepositoryAbstract{ - constructor(client_id,client_secret) { + constructor(client_id,client_secret,redirect_URI) { super(); this.client_id = client_id this.client_secret = client_secret + this.redirect_uri = redirect_URI } @@ -41,4 +42,42 @@ module.exports = class extends spotifyRepositoryAbstract{ }) } -}; + refreshToken(token) { + const payload = new URLSearchParams() + payload.append('grant_type', 'refresh_token'); + payload.append('refresh_token', token); + return axios.post('https://accounts.spotify.com/api/token',payload,{ + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + auth: { + username: this.client_id, + password: this.client_secret, + }, + }) + .then((response) => { + return response.data + }) + } + getToken(code) { + const payload = new URLSearchParams() + payload.append('grant_type','authorization_code') + payload.append('redirect_uri',this.redirect_uri) + payload.append('code',code) + return axios.post('https://accounts.spotify.com/api/token',payload, + { + validateStatus: function (status) { + return true; + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + (new Buffer.from(this.client_id + ':' + this.client_secret).toString('base64')) + }, + }) + .then((response) => { + return response.data + }) + } + + + }; diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index 23f1d02..957a4da 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -15,17 +15,18 @@ module.exports = class extends userRepository { } async persist(userEntity) { - const { pseudo, email, password,alias, bio, id_role,id_etat,spotifyToken } = userEntity; + const { pseudo, email, password,alias, bio, id_role,id_etat,token,refresh_token } = userEntity; const seqUser = await this.model.create({ pseudo : pseudo, email : email, alias : alias, bio : bio, password : password, - token_spotify : spotifyToken, + token : token, + refresh_token : refresh_token, id_role : id_role, id_etat : id_etat }); - + console.log(seqUser) await seqUser.save(); return this.createUser(seqUser) } @@ -56,17 +57,20 @@ module.exports = class extends userRepository { seqUser.photo, seqUser.photo_temporaire, seqUser.password, - seqUser.token_spotify, + seqUser.token, + seqUser.refresh_token, seqUser.id_role, seqUser.ban_until); } async getByEmailOrPseudo(email, pseudo){ + console.log(email) + console.log(pseudo) const seqUser = await this.model.findOne({ where: { - [Op.or]: [ - { pseudo : email}, - { email : pseudo} - ] + [Op.or]: { + pseudo: pseudo, + email: email, + } } }); return this.createUser(seqUser) @@ -107,5 +111,32 @@ module.exports = class extends userRepository { ) return updatedRowsCount } + async updateUser(user){ + const { pseudo, email, password,alias, bio, id_role,id_etat,token,refresh_token } = user; + const seqUser = await this.model.findOne({ + where: { + id_utilisateur : user.id + } + }); + seqUser.pseudo = pseudo + seqUser.email = email + seqUser.alias = alias + seqUser.bio = bio + seqUser.password = password + seqUser.token = token + seqUser.refresh_token = refresh_token + seqUser.id_role = id_role + seqUser.id_etat = id_etat + seqUser.save() + } + async getSpotifyAuthUser(){ + let seqUsers = await this.model.findAll({ + where: { + token: {[Op.not]: null} + } + }); + seqUsers = seqUsers.map(item => this.createUser(item.dataValues)) + return seqUsers; + } }; diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 6a77f3f..34a0e24 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -9,7 +9,9 @@ module.exports = class { async getSpotifySearchList(query,filter,limit){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - + refreshToken(token) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index 44318ec..4a93042 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -25,4 +25,10 @@ module.exports = class { async addPreviewPath(id,path){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + updateUser(user){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getSpotifyAuthUser(){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/routine/refreshTokens.js b/lib/infrastructure/routine/refreshTokens.js new file mode 100644 index 0000000..9e3d271 --- /dev/null +++ b/lib/infrastructure/routine/refreshTokens.js @@ -0,0 +1,7 @@ +const refreshToken = require("../../application/use_cases/spotify/RefreshToken") +module.exports = async ({spotifyRepository, userRepository}) => { + const users = await userRepository.getSpotifyAuthUser() + for(let i = 0; i { // Create a server with a host and port @@ -70,7 +71,7 @@ const createServer = async () => { require('../../interfaces/routes/users'), require('../../interfaces/routes/spotify') ]); - + refreshTokens(serviceLocator) return server; }; diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index c080570..bd6b51b 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -4,6 +4,9 @@ const CreateUser = require('../../application/use_cases/user/CreateUser'); const GetAccessToken = require('../../application/use_cases/security/GetAccessToken'); const handleError = require("./utils/handleError") const uploadPreview = require("../../application/use_cases/user/uploadPreview") +const refreshToken = require("../../application/use_cases/spotify/RefreshToken") +const getToken = require("../../application/use_cases/spotify/GetToken") +const {get} = require("axios"); module.exports = { async createUser(request,handler) { @@ -13,7 +16,15 @@ module.exports = { // Input let { pseudo, email, alias, bio, password,spotifyToken } = request.payload - await CreateUser(pseudo, email, alias, bio, password,spotifyToken, serviceLocator); + let token = null + let refresh_token = null + if(spotifyToken){ + const tokens = await getToken(spotifyToken,serviceLocator) + token = tokens.token + refresh_token = tokens.refresh_token + } + const user = await CreateUser(pseudo, email, alias, bio, password,token,refresh_token, serviceLocator); + refreshToken(user.id,serviceLocator) return handler.response('Votre compte a bien été crée').code(200) }catch(error){ return handleError(error) diff --git a/test/integration/users.test.js b/test/integration/users.test.js index da2a14a..7e2ed1f 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -6,23 +6,33 @@ const strategy = require("../../lib/infrastructure/config/strategy"); const Jwt = require("@hapi/jwt"); const fs = require('fs') const FormData = require('form-data'); - +require('dotenv').config() let server const mockUserRepository = {} const mockAccesTokenManager = {} -require('dotenv').config() +const mockSpotifyRepository = {} + + + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => { return null }) mockUserRepository.persist = jest.fn((test) =>{ return test }) +mockUserRepository.updateUser = jest.fn(user => user) mockUserRepository.getPreviewPath = jest.fn((test) => null) mockUserRepository.addPreviewPath = jest.fn((test,test2) => null) mockUserRepository.getByUser = jest.fn((test) => { - console.log("test") return 1 }) + + + +mockSpotifyRepository.refreshToken = jest.fn(refresh => refresh) +mockSpotifyRepository.getToken= jest.fn(()=> 1) + + mockAccesTokenManager.generate = ((test) =>{return ''}) describe('user route', () => { @@ -33,7 +43,8 @@ describe('user route', () => { }); server.app.serviceLocator = { userRepository: mockUserRepository, - accessTokenManager:mockAccesTokenManager + accessTokenManager:mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, } server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); @@ -63,6 +74,7 @@ describe('user route', () => { bio: "dzada" } }); + console.log(res) expect(res.statusCode).toBe(200); }); @@ -79,7 +91,6 @@ describe('user route', () => { pseudo: "testsss", alias: "testsss", password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", bio: "dzada" } }); diff --git a/test/unit/application/usecase/fixtures/searchFixture.js b/test/unit/application/usecase/fixtures/searchFixture.js index 8b81274..8bc4ccb 100644 --- a/test/unit/application/usecase/fixtures/searchFixture.js +++ b/test/unit/application/usecase/fixtures/searchFixture.js @@ -29,6 +29,7 @@ const mockUser = new User( 'path/to/photo', 'passwordtest', 'spotifyToken', + 'spotifyToken', 2, new Date("10-06-2003") ) diff --git a/test/unit/application/usecase/spotify/GetToken.test.js b/test/unit/application/usecase/spotify/GetToken.test.js new file mode 100644 index 0000000..2788091 --- /dev/null +++ b/test/unit/application/usecase/spotify/GetToken.test.js @@ -0,0 +1,28 @@ +const getToken = require("../../../../../lib/application/use_cases/spotify/GetToken") +const createUser = require("../../../../../lib/application/use_cases/user/CreateUser"); +const mockSpotifyRepository = {} + +describe("GetToken", () =>{ + describe("GetToken valid ", ()=>{ + + it("should return valid json", async ()=>{ + expectedSpotifyCode = { + access_token: "access_token", + refresh_token: "refresh_token", + } + mockSpotifyRepository.getToken = jest.fn((code )=> {return expectedSpotifyCode}) + const result = await getToken("code",{spotifyRepository: mockSpotifyRepository}) + }) + }) + describe("GetToken invalid ", ()=>{ + it("should return error", async ()=>{ + expectedSpotifyCode = { + error: "something" + } + mockSpotifyRepository.getToken = jest.fn((code )=> {return expectedSpotifyCode}) + await expect(getToken("code",{spotifyRepository: mockSpotifyRepository}) + ).rejects.toThrow('something') + + }) + }) +}) diff --git a/test/unit/application/usecase/user/user.test.js b/test/unit/application/usecase/user/user.test.js index 938560d..9187891 100644 --- a/test/unit/application/usecase/user/user.test.js +++ b/test/unit/application/usecase/user/user.test.js @@ -15,6 +15,7 @@ const persistedUser = new User( 'path/to/file', 'passwordtest', 'spotifyToken', + 'refreshToken', 2, new Date("10-06-2003") ) @@ -32,6 +33,7 @@ describe('createUser', () =>{ 'testbio', 'passwordtest', 'spotifyToken', + 'refreshToken', {userRepository : mockUserRepository} ) const mockResult = mockUserRepository.persist.mock.calls[0][0] @@ -41,7 +43,8 @@ describe('createUser', () =>{ expect(mockResult.email).toBe(persistedUser.email) expect(mockResult.alias).toBe(persistedUser.alias) expect(mockResult.bio).toBe(persistedUser.bio) - expect(mockResult.spotifyToken).toBe(persistedUser.spotifyToken) + expect(mockResult.token).toBe(persistedUser.token) + expect(mockResult.refresh_token).toBe(persistedUser.refresh_token) expect(mockResult.id_role).toBe(2) expect(mockResult.photo).toBe(null) expect(mockResult.tempPhoto).toBe(null) @@ -59,6 +62,7 @@ describe('createUser', () =>{ '', '', '', + '', {userRepository: mockUserRepository} )).rejects.toThrow('Email ou Pseudo déjà existant') }) @@ -73,6 +77,7 @@ describe('createUser', () =>{ '', '', '', + '', {userRepository: mockUserRepository} )).rejects.toThrow('Email ou Pseudo déjà existant') }) From fade0c2d6d85f03dec89131f76368701216ca57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Wed, 20 Dec 2023 00:06:22 +0100 Subject: [PATCH 09/67] added static image server (#16) --- lib/infrastructure/webserver/server.js | 12 +++-- lib/interfaces/controllers/ImageController.js | 22 --------- .../controllers/UploadController.js | 8 ++++ lib/interfaces/routes/image.js | 47 ------------------- lib/interfaces/routes/upload.js | 30 ++++++++++++ 5 files changed, 47 insertions(+), 72 deletions(-) delete mode 100644 lib/interfaces/controllers/ImageController.js create mode 100644 lib/interfaces/controllers/UploadController.js delete mode 100644 lib/interfaces/routes/image.js create mode 100644 lib/interfaces/routes/upload.js diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 14f3859..4557826 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -11,11 +11,16 @@ const serviceLocator = require('../../infrastructure/config/service-locator') const Jwt = require('@hapi/jwt'); const strategy = require("../config/strategy") const refreshTokens = require("../routine/refreshTokens") +const Path = require("path"); const createServer = async () => { - // Create a server with a host and port const server = Hapi.server({ - port: process.env.PORT || 3000 + port: process.env.PORT || 3000, + routes:{ + files: { + relativeTo: Path.join(__dirname.split("\\").slice(0,-3).join("\\")+'\\upload') + } + } }); // Register vendors plugins @@ -69,7 +74,8 @@ const createServer = async () => { await server.register([ require('../../interfaces/routes/users'), - require('../../interfaces/routes/spotify') + require('../../interfaces/routes/spotify'), + require('../../interfaces/routes/upload') ]); refreshTokens(serviceLocator) return server; diff --git a/lib/interfaces/controllers/ImageController.js b/lib/interfaces/controllers/ImageController.js deleted file mode 100644 index c74d821..0000000 --- a/lib/interfaces/controllers/ImageController.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const uploadImage = require('../../application/use_cases/image/uploadImage'); - -module.exports = { - - async upload(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const {image} = request.payload - const {path} = await uploadImage(image) - try{ - return handler - .response({ - url : `${request.server.info.uri}/${path}`, - }) - } - catch (error){ - console.log(error) - return handler.response(error).code(500) - } - } -}; diff --git a/lib/interfaces/controllers/UploadController.js b/lib/interfaces/controllers/UploadController.js new file mode 100644 index 0000000..198e41e --- /dev/null +++ b/lib/interfaces/controllers/UploadController.js @@ -0,0 +1,8 @@ +const Path = require('path'); +module.exports = { + async getImage(request,handler){ + const {filename} = request.params + + return handler.file(filename) + } +} \ No newline at end of file diff --git a/lib/interfaces/routes/image.js b/lib/interfaces/routes/image.js deleted file mode 100644 index 249bbe7..0000000 --- a/lib/interfaces/routes/image.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; -const Joi = require('@hapi/joi') -const ImageController = require("../controllers/ImageController") -const MAX_BYTE_SIZE =20971520 -module.exports = { - name: 'image', - version: '1.0.0', - register: async (server) => { - server.route([ - { - method: 'POST', - path: '/image/upload', - handler: ImageController.upload, - options: { - payload: { - maxBytes: MAX_BYTE_SIZE, // Set your desired maximum payload size in bytes - output: 'stream', - parse: true, - allow: 'multipart/form-data', - multipart: true, // Set multipart to true for handling file uploads - }, - description: 'upload an image', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description : 'Success'}, - 204: {description : 'No content'}, - 401: {description : 'Unauthorized'}, - 403: {description : 'forbidden'}, - 404: {description : 'Ressource not found'}, - 500: {description : 'Internal server error'}, - 502: {description : 'bad gateway'}, - 503: {description : 'Service unavailable'}, - } - } - }, - validate: { - payload: Joi.object().keys({ - image: Joi.any().required(), - }) - } - }, - }, - ]); - } -}; \ No newline at end of file diff --git a/lib/interfaces/routes/upload.js b/lib/interfaces/routes/upload.js new file mode 100644 index 0000000..f0d085e --- /dev/null +++ b/lib/interfaces/routes/upload.js @@ -0,0 +1,30 @@ +'use strict'; +const {userSignUp, userSignIn} = require('../../domain/entity/UserEntity') +const UploadController = require('../controllers/UploadController'); + +module.exports = { + name: 'upload', + version: '1.0.0', + register: async (server) => { + + server.route([ + { + method: 'GET', + path: '/upload/{filename}', + handler: UploadController.getImage, + options: { + description: 'Create a static image', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + } + } + }, + }, + }, + ]); + } +}; \ No newline at end of file From 4d679d5c9d332d6905edef62281947dba6674914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:15:57 +0100 Subject: [PATCH 10/67] registering refactor (#17) --- lib/7dc6da2fbc3e960f6bf7aa15fa68a16b.png | Bin 0 -> 5261 bytes .../use_cases/security/GetAccessToken.js | 2 +- .../use_cases/spotify/GetAuthURL.js | 5 +- .../use_cases/spotify/RefreshToken.js | 12 +- .../use_cases/user/CompleteAccount.js | 36 ++ lib/application/use_cases/user/CreateUser.js | 63 +-- .../use_cases/user/getUserByConfirmToken.js | 5 + .../use_cases/user/getUserByPseudo.js | 5 + .../use_cases/user/uploadPreview.js | 4 +- lib/domain/entity/UserEntity.js | 6 +- lib/domain/model/User.js | 29 +- lib/domain/model/UserPublic.js | 16 + lib/infrastructure/config/service-locator.js | 4 +- .../orm/sequelize/models/Utilisateur.js | 9 +- .../repositories/NodemailerRepository.js | 23 + .../repositories/SpotifyRepository.js | 11 + .../repositories/UserRepository.js | 74 ++-- .../interfaces/MailRepositoryAbstract.js | 14 + .../interfaces/SpotifyRepositoryAbstract.js | 7 +- .../interfaces/UserRepositoryAbstract.js | 11 + lib/infrastructure/routine/refreshTokens.js | 2 +- .../routine/removeExpiredConfirmTokens.js | 3 + lib/infrastructure/webserver/server.js | 5 +- .../controllers/SpotifyController.js | 6 +- lib/interfaces/controllers/UsersController.js | 65 ++- lib/interfaces/routes/users.js | 75 +++- package-lock.json | 9 + package.json | 2 +- test/integration/users.test.js | 405 +++++++++--------- .../usecase/spotify/GetToken.test.js | 13 +- .../usecase/user/completeAccount.test.js | 93 ++++ .../usecase/user/createUser.test.js | 74 ++++ .../usecase/user/getAccessToken.test.js | 90 ++++ .../user/getUserByConfirmToken.test.js | 44 ++ .../usecase/user/getUserByPseudo.test.js | 44 ++ .../usecase/user/uploadPreview.test.js | 41 +- .../application/usecase/user/user.test.js | 163 ------- .../application/usecase/utils/catchError.js | 9 + .../repositories/DocumentRepository.test.js | 2 +- 39 files changed, 981 insertions(+), 500 deletions(-) create mode 100644 lib/7dc6da2fbc3e960f6bf7aa15fa68a16b.png create mode 100644 lib/application/use_cases/user/CompleteAccount.js create mode 100644 lib/application/use_cases/user/getUserByConfirmToken.js create mode 100644 lib/application/use_cases/user/getUserByPseudo.js create mode 100644 lib/domain/model/UserPublic.js create mode 100644 lib/infrastructure/repositories/NodemailerRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/MailRepositoryAbstract.js create mode 100644 lib/infrastructure/routine/removeExpiredConfirmTokens.js create mode 100644 test/unit/application/usecase/user/completeAccount.test.js create mode 100644 test/unit/application/usecase/user/createUser.test.js create mode 100644 test/unit/application/usecase/user/getAccessToken.test.js create mode 100644 test/unit/application/usecase/user/getUserByConfirmToken.test.js create mode 100644 test/unit/application/usecase/user/getUserByPseudo.test.js delete mode 100644 test/unit/application/usecase/user/user.test.js create mode 100644 test/unit/application/usecase/utils/catchError.js diff --git a/lib/7dc6da2fbc3e960f6bf7aa15fa68a16b.png b/lib/7dc6da2fbc3e960f6bf7aa15fa68a16b.png new file mode 100644 index 0000000000000000000000000000000000000000..09af143864548d230d5032943d652b41867c8986 GIT binary patch literal 5261 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 Ih;`I|0UjFuhX4Qo literal 0 HcmV?d00001 diff --git a/lib/application/use_cases/security/GetAccessToken.js b/lib/application/use_cases/security/GetAccessToken.js index cef3d32..ecbb6a6 100644 --- a/lib/application/use_cases/security/GetAccessToken.js +++ b/lib/application/use_cases/security/GetAccessToken.js @@ -9,7 +9,7 @@ module.exports = async (email, password, { userRepository, accessTokenManager }) return accessTokenManager.generate({ sub: 'my-sub', // needs to match definition above - value: user.id, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user + value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user aud: 'urn:audience:test', // needs to match definition above iss: 'urn:issuer:test' // needs to match definition above }); diff --git a/lib/application/use_cases/spotify/GetAuthURL.js b/lib/application/use_cases/spotify/GetAuthURL.js index d546ee1..0de2487 100644 --- a/lib/application/use_cases/spotify/GetAuthURL.js +++ b/lib/application/use_cases/spotify/GetAuthURL.js @@ -17,9 +17,6 @@ const spotifyScopes = [ 'user-read-recently-played', 'ugc-image-upload' ]; -const crypto = require("crypto"); - -module.exports = async ({spotifyRepository}) => { - const state = crypto.randomBytes(16).toString('hex') +module.exports = async () => { return encodeURI(`https://accounts.spotify.com/authorize?client_id=${process.env.CLIENT_ID}&response_type=${'code'}&redirect_uri=${encodeURI(process.env.SPOTIFY_AUTH_REDIRECT)}&show_dialog=${'true'}&scope=${spotifyScopes.join(' ')}`) } \ No newline at end of file diff --git a/lib/application/use_cases/spotify/RefreshToken.js b/lib/application/use_cases/spotify/RefreshToken.js index 72be8b4..91366ad 100644 --- a/lib/application/use_cases/spotify/RefreshToken.js +++ b/lib/application/use_cases/spotify/RefreshToken.js @@ -1,10 +1,14 @@ -module.exports = async (id,{spotifyRepository,userRepository}) => { - setInterval(async ()=>{ +module.exports = (id,instant_refresh,{spotifyRepository,userRepository}) => { + const action = async ()=>{ const user = await userRepository.getByUser(id) - console.log(user) - const {access_token} = await spotifyRepository.refreshToken(user.refresh_token) user.token = access_token userRepository.updateUser(user) + } + if(instant_refresh){ + action() + } + setInterval(()=>{ + action() },3500*1000) } \ No newline at end of file diff --git a/lib/application/use_cases/user/CompleteAccount.js b/lib/application/use_cases/user/CompleteAccount.js new file mode 100644 index 0000000..33951f7 --- /dev/null +++ b/lib/application/use_cases/user/CompleteAccount.js @@ -0,0 +1,36 @@ +'use strict'; + +const User = require('../../../domain/model/User'); +const bcrypt = require("bcrypt"); +const rolesEnum = require('../../../domain/model/utils/RolesEnum') +const throwStatusCode = require("../utils/throwStatusCode") +module.exports = async (pseudo,alias, bio, password, photo,confirmToken, { userRepository,documentRepository }) => { + password = await bcrypt.hash(password,10) + if(!password) { + throwStatusCode('500','Internal server error') + } + const userTest = await userRepository.getByEmailOrPseudo(pseudo,pseudo) + if(userTest){ + throwStatusCode(403,'Pseudo déjà existant') + } + alias = alias ? alias : pseudo + const user = await userRepository.getByConfirmToken(confirmToken); + if(!user) { + throwStatusCode(400,'Token invalide') + } + user.alias = alias + user.pseudo = pseudo + user.bio = bio + user.password = password + user.confirm_token = null + user.confirmed = true + if(user.photo_temporaire && photo){ + documentRepository.deleteFile(user.photo_temporaire) + user.photo_temporaire = photo + user.photo = photo + } + return userRepository.updateUser(user) + + + +}; diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index 6ee7fac..feaf7c1 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -4,30 +4,45 @@ const User = require('../../../domain/model/User'); const bcrypt = require("bcrypt"); const rolesEnum = require('../../../domain/model/utils/RolesEnum') const throwStatusCode = require("../utils/throwStatusCode") -module.exports = async (pseudo, email,alias, bio, password,token,refreshToken, { userRepository }) => { - password = await bcrypt.hash(password,10) - if(!password) { - throwStatusCode('500','Internal server error') - } - const userTest = await userRepository.getByEmailOrPseudo(email,pseudo) - if(userTest){ - throwStatusCode('403','Email ou Pseudo déjà existant') - } - const user = new User( - null, - pseudo, - email, - alias, - bio, - null, - null, - password, - token, - refreshToken, - rolesEnum.UTILISATEUR, - null); - return userRepository.persist(user) - +const crypto = require("crypto"); +module.exports = async (email,spotify_code, { userRepository,mailRepository,spotifyRepository}) => { + let display_name,confirm_token,image,access_token,refresh_token,error + if(spotify_code){ + ({access_token, refresh_token,error} = await spotifyRepository.getToken(spotify_code)) + if(error){ + throwStatusCode(400,error) + } + ({email,display_name,image} = await spotifyRepository.getAccountData(access_token)) + image = image?.at(-1)?.url + } + const userTest = await userRepository.getByEmailOrPseudo(email,email) + if(userTest){ + throwStatusCode(403,'Email déjà existant') + } + confirm_token = crypto.randomBytes(16).toString('hex') + const userRaw = { + email, + alias: display_name ? display_name : null, + photo_temporaire: image ? image : null, + confirmed:false, + token: access_token, + refresh_token, + confirm_token + } + let user = new User( + userRaw); + user = await userRepository.persist(user) + const mailOptions = { + from: process.env.MAILER_EMAIL, + to: email, + subject: 'confirmToken', + text: confirm_token + }; + await mailRepository.send(mailOptions) + setTimeout(()=>{ + userRepository.removeUserByConfirmToken(confirm_token) + },1000*60*24) + return user }; diff --git a/lib/application/use_cases/user/getUserByConfirmToken.js b/lib/application/use_cases/user/getUserByConfirmToken.js new file mode 100644 index 0000000..7dd23b2 --- /dev/null +++ b/lib/application/use_cases/user/getUserByConfirmToken.js @@ -0,0 +1,5 @@ +const UserPublic = require("../../../domain/model/UserPublic") +module.exports = async (confirmToken,{userRepository}) =>{ + const user = userRepository.getUserByConfirmToken(confirmToken) + return user ? new UserPublic(user) : null +} \ No newline at end of file diff --git a/lib/application/use_cases/user/getUserByPseudo.js b/lib/application/use_cases/user/getUserByPseudo.js new file mode 100644 index 0000000..65bfaa5 --- /dev/null +++ b/lib/application/use_cases/user/getUserByPseudo.js @@ -0,0 +1,5 @@ +const UserPublic = require("../../../domain/model/UserPublic") +module.exports = async (pseudo,{userRepository}) =>{ + const user = userRepository.getByEmailOrPseudo(pseudo,pseudo) + return user ? new UserPublic(user) : null +} \ No newline at end of file diff --git a/lib/application/use_cases/user/uploadPreview.js b/lib/application/use_cases/user/uploadPreview.js index fd4f604..13dfa18 100644 --- a/lib/application/use_cases/user/uploadPreview.js +++ b/lib/application/use_cases/user/uploadPreview.js @@ -2,9 +2,9 @@ const isImage = require("../utils/isImage") const throwStatusCode = require("../utils/throwStatusCode") module.exports = async (file,token,{accessTokenManager, userRepository,documentRepository}) => { - if(!isImage(file)) throwStatusCode('415',"le fichier fourni n'est pas une image") + if(!isImage(file)) throwStatusCode(415,"le fichier fourni n'est pas une image") const id = accessTokenManager.decode(token)?.value - if(! await userRepository.getByUser(id)) throwStatusCode('401',"votre token d'authentification n'est pas le bon") + if(! await userRepository.getByUser(id)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") const previewPath = await userRepository.getPreviewPath(id) if(previewPath) { documentRepository.deleteFile(previewPath) diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index 9d7714b..32f6dfa 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -6,16 +6,16 @@ const userSignIn = Joi.object().keys({ }) const userSignUp = Joi.object().keys({ - email: Joi.string().email().min(10).max(40).required().error(validationErrror("email","l'email est invalide")), pseudo: Joi.string().min(3).max(15).required().custom((value, helpers) => { if (value.includes('@')) { return helpers.error('any.invalid'); } return value; }, 'pseudo validation').error(validationErrror("pseudo","le pseudo doit être compris entre 3 et 15 caractère")), - alias: Joi.string().min(3).max(15).required().error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), + alias: Joi.string().min(3).max(15).error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")), - spotifyToken: Joi.string().max(1000), + photo: Joi.string().max(500), + confirmToken: Joi.string().max(50), bio:Joi.string().max(1500).error(validationErrror("bio","le pseudo doit faire moins de 1500 caractères")), }) diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index 9191dfd..d1c2768 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -2,20 +2,21 @@ module.exports = class { - constructor(id = null, pseudo, email,alias, bio, photo,tempPhoto, password,token,refresh_token,id_role, banUntil) { - this.id = id; - this.pseudo = pseudo; - this.email = email; - this.alias = alias - this.photo = photo - this.tempPhoto = tempPhoto - this.token = token - this.refresh_token = refresh_token - this.password = password; - this.password = password; - this.id_role = id_role; - this.banUntil = banUntil - this.type = 'user' + constructor(userRaw) { + this.id_utilisateur = userRaw?.id_utilisateur; + this.pseudo = userRaw?.pseudo; + this.email = userRaw?.email; + this.alias = userRaw?.alias + this.photo = userRaw?.photo + this.photo_temporaire = userRaw?.photo_temporaire + this.token = userRaw?.token + this.refresh_token = userRaw?.refresh_token + this.password = userRaw?.password; + this.id_role = userRaw?.id_role; + this.ban_until = userRaw?.ban_until + this.confirmed= userRaw?.confirmed + this.confirm_token = userRaw?.confirm_token + this.type = 'user' } }; \ No newline at end of file diff --git a/lib/domain/model/UserPublic.js b/lib/domain/model/UserPublic.js new file mode 100644 index 0000000..80cddab --- /dev/null +++ b/lib/domain/model/UserPublic.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports = class { + + constructor(userRaw) { + this.pseudo = userRaw?.pseudo; + this.email = userRaw?.email; + this.alias = userRaw?.alias + this.photo = userRaw?.photo + this.photo_temporaire = userRaw?.photo_temporaire + this.id_role = userRaw?.id_role; + this.ban_until = userRaw?.ban_until + this.type = 'user' + } + +}; \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index f75bf4f..f60e296 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -6,13 +6,15 @@ const UserRepository= require('../repositories/UserRepository'); const spotifyRepository= require('../repositories/SpotifyRepository'); const JwtAccessTokenManager = require('../security/JwtAccessTokenManager'); const documentRepository= require('../repositories/DocumentRepository'); +const NodemailerRepository= require('../repositories/NodemailerRepository'); function buildBeans() { return { accessTokenManager: new JwtAccessTokenManager(), userRepository: new UserRepository(), spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET,process.env.SPOTIFY_AUTH_REDIRECT), - documentRepository: new documentRepository() + documentRepository: new documentRepository(), + mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS) }; } diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index 0dbfca5..fcb10bf 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -12,16 +12,13 @@ module.exports = (sequelize) => { }, pseudo: { type: DataTypes.STRING(15), - allowNull: false, unique: true, }, alias: { type: DataTypes.STRING(15), - allowNull: false, }, password: { type: DataTypes.STRING(200 ), - allowNull: false, }, email: { type: DataTypes.STRING(30), @@ -51,6 +48,12 @@ module.exports = (sequelize) => { bio: { type: DataTypes.STRING(1500), }, + confirmed: { + type: DataTypes.BOOLEAN + }, + confirm_token: { + type: DataTypes.STRING(50), + }, }, { freezeTableName: true,} ); diff --git a/lib/infrastructure/repositories/NodemailerRepository.js b/lib/infrastructure/repositories/NodemailerRepository.js new file mode 100644 index 0000000..3f68eec --- /dev/null +++ b/lib/infrastructure/repositories/NodemailerRepository.js @@ -0,0 +1,23 @@ +'use strict'; +const MailRepositoryAbstract = require('./interfaces/MailRepositoryAbstract') +const nodemailer = require('nodemailer') +module.exports = class extends MailRepositoryAbstract{ + constructor(service, user, pass) { + super(); + this.transporter = nodemailer.createTransport({ + service, + auth: { + user, + pass + } + }) + } + send(config) { + this.transporter.sendMail(config) + } + + + + + +}; diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index ee7b1ca..30c9567 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -78,6 +78,17 @@ module.exports = class extends spotifyRepositoryAbstract{ return response.data }) } + getAccountData(accessToken) { + return axios.get('https://api.spotify.com/v1/me', { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) + .then(response => { + return response.data + }) + } }; diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index 957a4da..a1acdfb 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -3,9 +3,7 @@ const sequelize = require('../orm/sequelize/sequelize'); const user = require('../../domain/model/User'); const userRepository = require('./interfaces/UserRepositoryAbstract'); -const { Op,col,literal } = require('sequelize'); -const {id} = require("../../domain/model/User"); -const {create} = require("underscore"); +const { Op} = require('sequelize'); module.exports = class extends userRepository { constructor() { @@ -15,18 +13,7 @@ module.exports = class extends userRepository { } async persist(userEntity) { - const { pseudo, email, password,alias, bio, id_role,id_etat,token,refresh_token } = userEntity; - const seqUser = await this.model.create({ - pseudo : pseudo, - email : email, - alias : alias, - bio : bio, - password : password, - token : token, - refresh_token : refresh_token, - id_role : id_role, - id_etat : id_etat }); - console.log(seqUser) + const seqUser = await this.model.create(userEntity); await seqUser.save(); return this.createUser(seqUser) } @@ -49,22 +36,9 @@ module.exports = class extends userRepository { createUser(seqUser){ if(!seqUser) return null - return new user(seqUser.id_utilisateur, - seqUser.pseudo, - seqUser.email, - seqUser.alias, - seqUser.bio, - seqUser.photo, - seqUser.photo_temporaire, - seqUser.password, - seqUser.token, - seqUser.refresh_token, - seqUser.id_role, - seqUser.ban_until); + return new user(seqUser); } async getByEmailOrPseudo(email, pseudo){ - console.log(email) - console.log(pseudo) const seqUser = await this.model.findOne({ where: { [Op.or]: { @@ -112,10 +86,10 @@ module.exports = class extends userRepository { return updatedRowsCount } async updateUser(user){ - const { pseudo, email, password,alias, bio, id_role,id_etat,token,refresh_token } = user; + const { pseudo, email, password,alias, bio, id_role,ban_until,token,refresh_token,confirmed,confirmToken,photo, photo_temporaire } = user; const seqUser = await this.model.findOne({ where: { - id_utilisateur : user.id + id_utilisateur : user.id_utilisateur } }); seqUser.pseudo = pseudo @@ -126,7 +100,11 @@ module.exports = class extends userRepository { seqUser.token = token seqUser.refresh_token = refresh_token seqUser.id_role = id_role - seqUser.id_etat = id_etat + seqUser.ban_until = ban_until + seqUser.confirmed = confirmed + seqUser.confirm_token = confirmToken + seqUser.photo = photo + seqUser.photo_temporaire = photo_temporaire seqUser.save() } async getSpotifyAuthUser(){ @@ -138,5 +116,37 @@ module.exports = class extends userRepository { seqUsers = seqUsers.map(item => this.createUser(item.dataValues)) return seqUsers; } + async removeUserByConfirmToken(confirmToken){ + const seqUsers = await this.model.findOne({ + where: { + confirm_token: confirmToken + } + }); + if(seqUsers) { + await seqUsers.destroy() + } + } + removeUncheckedAccounts() { + const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + this.model.destroy({ + where: { + [Op.and]: [ + {updatedAt: { + [Op.lt]: twentyFourHoursAgo + }}, + {confirmed: false} + ] + + } + }) + } + async getByConfirmToken(token) { + const seqUsers = await this.model.findOne({ + where: { + confirm_token: token + } + }); + return this.createUser(seqUsers) + } }; diff --git a/lib/infrastructure/repositories/interfaces/MailRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/MailRepositoryAbstract.js new file mode 100644 index 0000000..7bf6f6c --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/MailRepositoryAbstract.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = class { + + send(config) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + + + + + +}; diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 34a0e24..572c7c1 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -12,7 +12,12 @@ module.exports = class { refreshToken(token) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - + getToken(code) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getAccountData(accessToken){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index 4a93042..d09e58b 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -31,4 +31,15 @@ module.exports = class { getSpotifyAuthUser(){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + removeUserByConfirmToken(confirmToken){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + removeUncheckedAccounts() { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getByConfirmToken(token) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + }; diff --git a/lib/infrastructure/routine/refreshTokens.js b/lib/infrastructure/routine/refreshTokens.js index 9e3d271..8690fd0 100644 --- a/lib/infrastructure/routine/refreshTokens.js +++ b/lib/infrastructure/routine/refreshTokens.js @@ -2,6 +2,6 @@ const refreshToken = require("../../application/use_cases/spotify/RefreshToken") module.exports = async ({spotifyRepository, userRepository}) => { const users = await userRepository.getSpotifyAuthUser() for(let i = 0; i { + userRepository.removeUncheckedAccounts() +} \ No newline at end of file diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 4557826..c5ebbe7 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -10,8 +10,9 @@ const routes = require('../../interfaces/routes'); const serviceLocator = require('../../infrastructure/config/service-locator') const Jwt = require('@hapi/jwt'); const strategy = require("../config/strategy") -const refreshTokens = require("../routine/refreshTokens") const Path = require("path"); +const refreshTokens = require("../routine/refreshTokens") +const removeExpiredConfirmTokens = require("../routine/removeExpiredConfirmTokens") const createServer = async () => { // Create a server with a host and port const server = Hapi.server({ @@ -69,7 +70,6 @@ const createServer = async () => { server.app.serviceLocator = serviceLocator; - server.auth.strategy('jwt', 'jwt', strategy(serviceLocator)); await server.register([ @@ -78,6 +78,7 @@ const createServer = async () => { require('../../interfaces/routes/upload') ]); refreshTokens(serviceLocator) + removeExpiredConfirmTokens(serviceLocator) return server; }; diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index bf67a17..62a6f45 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -18,11 +18,7 @@ module.exports = { }, async getAuthURL(request,handler) { try{ - // Context - const serviceLocator = request.server.app.serviceLocator; - // Input - - const authURL = await getAuthURL(serviceLocator) + const authURL = await getAuthURL() return handler.response(authURL).code(200) }catch(error){ return handleError(error) diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index bd6b51b..e74c2f6 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -1,31 +1,25 @@ 'use strict'; const CreateUser = require('../../application/use_cases/user/CreateUser'); +const CompleteAccount = require('../../application/use_cases/user/CompleteAccount'); const GetAccessToken = require('../../application/use_cases/security/GetAccessToken'); const handleError = require("./utils/handleError") const uploadPreview = require("../../application/use_cases/user/uploadPreview") +const getUserByPseudo = require("../../application/use_cases/user/getUserByPseudo") +const getUserByConfirmToken = require("../../application/use_cases/user/getUserByConfirmToken") const refreshToken = require("../../application/use_cases/spotify/RefreshToken") -const getToken = require("../../application/use_cases/spotify/GetToken") -const {get} = require("axios"); +const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); module.exports = { - async createUser(request,handler) { + async confirmUser(request, handler) { try{ // Context const serviceLocator = request.server.app.serviceLocator; // Input - let { pseudo, email, alias, bio, password,spotifyToken } = request.payload - let token = null - let refresh_token = null - if(spotifyToken){ - const tokens = await getToken(spotifyToken,serviceLocator) - token = tokens.token - refresh_token = tokens.refresh_token - } - const user = await CreateUser(pseudo, email, alias, bio, password,token,refresh_token, serviceLocator); - refreshToken(user.id,serviceLocator) - return handler.response('Votre compte a bien été crée').code(200) + let { pseudo, photo, alias, bio, password,confirmToken } = request.payload + const user = await CompleteAccount(pseudo, alias, bio, password,photo,confirmToken, serviceLocator); + return handler.response('Votre compte a bien été validé').code(200) }catch(error){ return handleError(error) } @@ -59,10 +53,43 @@ module.exports = { catch (e){ return handleError(e) } - // Now 'token' contains the bearer token - - // Perform any necessary authentication logic here - - return handler.response('Access granted!'); + }, + async createUser(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const {email,spotify_code} = request.payload + try{ + const user = await CreateUser(email,spotify_code,serviceLocator); + if(spotify_code){ + refreshToken(user.id_utilisateur,false,serviceLocator) + } + return handler.response("compte créé").code(200) + } + catch (e){ + return handleError(e) + } + }, + async isUser(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const {pseudo} = request.query + try{ + const user = await getUserByPseudo(pseudo,serviceLocator); + return handler.response({isUser: !!user}).code(200) + } + catch (e){ + return handleError(e) + } + }, + async getUserByConfirmToken(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const {confirmToken} = request.query + try{ + const user = await getUserByConfirmToken(confirmToken,serviceLocator); + if(!user) + throwStatusCode(403,"no user") + return handler.response(user).code(200) + } + catch (e){ + return handleError(e) + } } }; diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index a01ccfb..388283f 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -11,10 +11,10 @@ module.exports = { server.route([ { method: 'POST', - path: '/users/signup', - handler: UsersController.createUser, + path: '/users/confirmUser', + handler: UsersController.confirmUser, options: { - description: 'Create a user', + description: 'Confirm a user', tags: ['api'], plugins: { 'hapi-swagger': { @@ -101,6 +101,75 @@ module.exports = { }) } }, + }, + { + method: 'POST', + path: '/users/createUser', + handler: UsersController.createUser, + options: { + description: 'send Verification email to create account', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + email: Joi.string().email().min(10).max(40), + spotify_code: Joi.string().max(1000), + }) + } + }, + }, + { + method: 'GET', + path: '/users/isUser', + handler: UsersController.isUser, + options: { + description: 'check if a user exists', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 500: {description : 'Internal server error'}, + } + } + }, + validate: { + query: Joi.object().keys({ + pseudo: Joi.string().min(3).max(15).required(), + }) + } + } + }, + { + method: 'GET', + path: '/users/getUserByConfirmToken', + handler: UsersController.getUserByConfirmToken, + options: { + description: 'get user info', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 403: {description : 'forbidden'}, + 500: {description: 'Internal server error'}, + } + } + }, + validate: { + query: Joi.object().keys({ + confirmToken: Joi.string().max(50).required(), + }) + } + } } ]); } diff --git a/package-lock.json b/package-lock.json index 37ccdd7..79b7f56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", "mysql2": "^3.6.0", + "nodemailer": "^6.9.7", "request": "^2.88.2", "require-directory": "^2.1.1", "sequelize": "^5.21.11", @@ -5486,6 +5487,14 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", diff --git a/package.json b/package.json index 58f0752..25d9699 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@hapi/inert": "^7.1.0", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", - "@hapi/inert": "^7.1.0", "@hapi/shot": "^6.0.1", "@hapi/vision": "^7.0.3", "axios": "^1.5.1", @@ -32,6 +31,7 @@ "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", "mysql2": "^3.6.0", + "nodemailer": "^6.9.7", "request": "^2.88.2", "require-directory": "^2.1.1", "sequelize": "^5.21.11", diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 7e2ed1f..27fd736 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -4,35 +4,17 @@ const User = require("../../lib/domain/model/User") const bcrypt = require("bcrypt"); const strategy = require("../../lib/infrastructure/config/strategy"); const Jwt = require("@hapi/jwt"); -const fs = require('fs') -const FormData = require('form-data'); +const {id_utilisateur} = require("../../lib/domain/model/User"); require('dotenv').config() let server const mockUserRepository = {} const mockAccesTokenManager = {} const mockSpotifyRepository = {} +const mockMailRepository = {} +const mockDocumentRepository = {} -mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => { - return null -}) -mockUserRepository.persist = jest.fn((test) =>{ - return test -}) -mockUserRepository.updateUser = jest.fn(user => user) -mockUserRepository.getPreviewPath = jest.fn((test) => null) -mockUserRepository.addPreviewPath = jest.fn((test,test2) => null) -mockUserRepository.getByUser = jest.fn((test) => { - return 1 -}) - - - -mockSpotifyRepository.refreshToken = jest.fn(refresh => refresh) -mockSpotifyRepository.getToken= jest.fn(()=> 1) - - mockAccesTokenManager.generate = ((test) =>{return ''}) describe('user route', () => { @@ -45,222 +27,204 @@ describe('user route', () => { userRepository: mockUserRepository, accessTokenManager:mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, + mailRepository: mockMailRepository, + documentRepository: mockDocumentRepository } server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); - await server.register([ require('../../lib/interfaces/routes/users'), ]); - - - }); afterEach(async () => { + jest.clearAllMocks(); await server.stop(); }); - describe("/users/signup", ()=>{ - it('should respond code 200', async () => { + describe("/users/createUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) + mockUserRepository.persist = jest.fn((test) => { + return {id_utilisateur: 1} + }) + mockMailRepository.send = jest.fn(option => null) + mockSpotifyRepository.getToken= jest.fn(()=> { + return {access_token: 1, refresh_token: 1} + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return { + email: "testemail@gmail.com", + display_name: 'display_name', + image: [{url:"testurl"}] + } + }) + it('should respond code 200 with email inscription', async () => { + const res = await server.inject({ method: 'POST', - url: '/users/signup', + url: '/users/createUser', payload: { email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "testsss", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" } }); - console.log(res) + expect(res.statusCode).toBe(200); + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); - - - it('should respond code 403', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => { - return {} - }) + it('should respond code 200 with spotify inscription', async () => { const res = await server.inject({ method: 'POST', - url: '/users/signup', + url: '/users/createUser', payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "testsss", - password: "pdazdazassworrdddd", - bio: "dzada" + spotify_code: "code", } }); - expect(res.statusCode).toBe(403); + expect(res.statusCode).toBe(200); + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(1) }); - - it('should respond code 400 with invalid email', async () => { + it('should respond code 400 with invalid spotify_code', async () => { + mockSpotifyRepository.getToken= jest.fn(()=> { + return {error: {}} + }) const res = await server.inject({ method: 'POST', - url: '/users/signup', + url: '/users/createUser', payload: { - email: "te", - pseudo: "testsss", - alias: "testsss", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" + spotify_code: "code", } }); expect(res.statusCode).toBe(400); - expect(JSON.parse(res.payload).validation.keys).toBe("email") + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); - - it('should respond code 400 with invalid pseudo', async () => { - const res1 = await server.inject({ - method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "test@sss", - alias: "testsss", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" - } - }); - const res2 = await server.inject({ - method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "te", - alias: "testsss", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" - } - }); - const res3 = await server.inject({ + it('should respond code 403 with already existing email', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> 'something') + const res = await server.inject({ method: 'POST', - url: '/users/signup', + url: '/users/createUser', payload: { email: "tesddesqt@gmaiL.com", - pseudo: 'a'.repeat(16), - alias: "testsss", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" } }); - expect(res1.statusCode).toBe(400); - expect(JSON.parse(res1.payload).validation.keys).toBe("pseudo") - expect(res2.statusCode).toBe(400); - expect(JSON.parse(res2.payload).validation.keys).toBe("pseudo") - expect(res3.statusCode).toBe(400); - expect(JSON.parse(res3.payload).validation.keys).toBe("pseudo") - + expect(res.statusCode).toBe(403); + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); - it('should respond code 400 with invalid alias', async () => { - const res1 = await server.inject({ - method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "te", - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" - } - }); - const res2 = await server.inject({ + }) + describe("/users/confirmUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + + mockUserRepository.getByConfirmToken = jest.fn(()=> { + return { + id_utilisateur:1, + photo_temporaire: "path" + } + }) + mockUserRepository.updateUser= jest.fn(user => user) + mockUserRepository.persist = jest.fn((test) => { + return {id_utilisateur: 1} + }) + mockDocumentRepository.deleteFile= jest.fn(()=> null) + it('should respond code 200 with confirmation and photo', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + password: "TestPassword", + confirmToken: "token", + photo:"path", + bio:"bio" + } + const res = await server.inject({ method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: 'a'.repeat(16), - password: "pdazdazassworrdddd", - spotifyToken: "tokeeeeen", - bio: "dzada" - } + url: '/users/confirmUser', + payload: payload }); - - expect(res1.statusCode).toBe(400); - expect(JSON.parse(res1.payload).validation.keys).toBe("alias") - expect(res2.statusCode).toBe(400); - expect(JSON.parse(res2.payload).validation.keys).toBe("alias") - + expect(res.statusCode).toBe(200); + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1) }); - - it('should respond code 400 with invalid password', async () => { - const res1 = await server.inject({ + it('should respond code 200 with confirmation', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + password: "TestPassword", + confirmToken: "token", + bio:"bio" + } + const res = await server.inject({ method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "testsss", - password: "aa", - spotifyToken: "tokeeeeen", - bio: "dzada" - } + url: '/users/confirmUser', + payload: payload }); - const res2 = await server.inject({ + expect(res.statusCode).toBe(200); + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0) + }); + it('should respond code 400', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) + mockUserRepository.getByConfirmToken = jest.fn(()=>null) + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + password: "TestPassword", + confirmToken: "token", + bio:"bio" + } + const res = await server.inject({ method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "testsss", - password: "a".repeat(31), - spotifyToken: "tokeeeeen", - bio: "dzada" - } + url: '/users/confirmUser', + payload: payload }); - - expect(res1.statusCode).toBe(400); - expect(JSON.parse(res1.payload).validation.keys).toBe("password") - expect(res2.statusCode).toBe(400); - expect(JSON.parse(res2.payload).validation.keys).toBe("password") + expect(res.statusCode).toBe(400); }); - - it('should respond code 400 with invalid bio', async () => { - const res1 = await server.inject({ + it('should respond code 403', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>'someting') + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + password: "TestPassword", + confirmToken: "token", + bio:"bio" + } + const res = await server.inject({ method: 'POST', - url: '/users/signup', - payload: { - email: "tesddesqt@gmaiL.com", - pseudo: "testsss", - alias: "testsss", - password: "aaaaaaaaaaa", - spotifyToken: "tokeeeeen", - bio: "a".repeat(1501) - } + url: '/users/confirmUser', + payload: payload }); - - - expect(res1.statusCode).toBe(400); - expect(JSON.parse(res1.payload).validation.keys).toBe("bio") + expect(res.statusCode).toBe(403); }); }) describe("/users/signin", ()=> { - - + afterEach(()=>{ + jest.clearAllMocks(); + }) + mockUserRepository.getByUser = jest.fn((test) => { + return 1 + }) + mockAccesTokenManager.generate = ((test) =>{return ''}) it('should respond code 200', async () => { const password = 'password' - const fetchedUser = new User( - "id", - "pseudo", - "email", - "alias", - "bio", - "path/to/file", - "path/to/file", - await bcrypt.hash(password,10), - "token", - 1, - new Date("10-06-2003")) + const mockUserRaw = { + id_utilisateur:"id", + pseudo:"pseudo", + email:"email", + alias:"alias", + bio:"bio", + photo:"path/to/file", + photo_temporaire:"path/to/file", + password:await bcrypt.hash(password,10), + token:"token", + id_role:1, + ban_until:new Date("10-06-2003"), + } + const fetchedUser = new User(mockUserRaw) mockUserRepository.getByIdent = jest.fn((ident) =>{ return fetchedUser @@ -280,18 +244,20 @@ describe('user route', () => { }) it('should respond code 401 bad password', async () => { const password = 'password' - const fetchedUser = new User( - "id", - "pseudo", - "email", - "alias", - "bio", - "path/to/file", - "path/to/file", - await bcrypt.hash(password,10), - "token", - 1, - new Date("10-06-2003")) + const mockUserRaw = { + id_utilisateur:"id", + pseudo:"pseudo", + email:"email", + alias:"alias", + bio:"bio", + photo:"path/to/file", + photo_temporaire:"path/to/file", + password:await bcrypt.hash(password,10), + token:"token", + id_role:1, + ban_until:new Date("10-06-2003"), + } + const fetchedUser = new User(mockUserRaw) mockUserRepository.getByIdent = jest.fn((ident) =>{ return fetchedUser @@ -345,4 +311,53 @@ describe('user route', () => { expect(res.statusCode).toBe(400); }) }) + describe("/users/isUser", ()=> { + afterEach(()=>{ + jest.clearAllMocks(); + }) + it('should return true', async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{ + return {id_utilisateur:1} + }) + const res = await server.inject({ + method: 'GET', + url: '/users/isUser?pseudo=test' + }) + expect(res.statusCode).toBe(200); + expect(res.result.isUser).toBe(true) + }) + it('should return false', async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{ + return null + }) + const res = await server.inject({ + method: 'GET', + url: '/users/isUser?pseudo=test' + }) + expect(res.statusCode).toBe(200); + expect(res.result.isUser).toBe(false) + }) + }) + describe("/users/getUserByConfirmToken", ()=> { + it('should return valid code 200', async ()=>{ + mockUserRepository.getUserByConfirmToken = jest.fn(()=>{ + return {id_utilisateur:1} + }) + const res = await server.inject({ + method: 'GET', + url: '/users/getUserByConfirmToken?confirmToken=test' + }) + expect(res.statusCode).toBe(200); + }) + it('should return invalid code 403', async ()=>{ + mockUserRepository.getUserByConfirmToken = jest.fn(()=>{ + return null + }) + const res = await server.inject({ + method: 'GET', + url: '/users/getUserByConfirmToken?confirmToken=test' + }) + expect(res.statusCode).toBe(403); + }) + }) }); diff --git a/test/unit/application/usecase/spotify/GetToken.test.js b/test/unit/application/usecase/spotify/GetToken.test.js index 2788091..16bf5d3 100644 --- a/test/unit/application/usecase/spotify/GetToken.test.js +++ b/test/unit/application/usecase/spotify/GetToken.test.js @@ -1,12 +1,12 @@ const getToken = require("../../../../../lib/application/use_cases/spotify/GetToken") -const createUser = require("../../../../../lib/application/use_cases/user/CreateUser"); +const catchError = require("../utils/catchError") const mockSpotifyRepository = {} describe("GetToken", () =>{ describe("GetToken valid ", ()=>{ it("should return valid json", async ()=>{ - expectedSpotifyCode = { + const expectedSpotifyCode = { access_token: "access_token", refresh_token: "refresh_token", } @@ -16,13 +16,14 @@ describe("GetToken", () =>{ }) describe("GetToken invalid ", ()=>{ it("should return error", async ()=>{ - expectedSpotifyCode = { + const expectedSpotifyCode = { error: "something" } mockSpotifyRepository.getToken = jest.fn((code )=> {return expectedSpotifyCode}) - await expect(getToken("code",{spotifyRepository: mockSpotifyRepository}) - ).rejects.toThrow('something') - + const error = await catchError(async ()=>{ + await getToken("code",{spotifyRepository: mockSpotifyRepository}) + }) + expect(error.code).toBe(400) }) }) }) diff --git a/test/unit/application/usecase/user/completeAccount.test.js b/test/unit/application/usecase/user/completeAccount.test.js new file mode 100644 index 0000000..6e57de9 --- /dev/null +++ b/test/unit/application/usecase/user/completeAccount.test.js @@ -0,0 +1,93 @@ +const completeAccount = require('../../../../../lib/application/use_cases/user/CompleteAccount') +const catchError = require("../utils/catchError") +const mockUserRepository = {} +const mockDocumentRepository = {} +const serviceLocator = { + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository, +} +describe('createUser', () =>{ + const pseudo= 'testPseudo' + const alias= 'testAlias' + const bio= 'testBio' + const password= 'testPassword' + const confirm_token= 'confirmToken' + const photo = "otherpath/to/file" + const email = "testemail@gmail.com" + const photo_temporaire = "path/to/file" + const confirmed = false + const id_utilisateur = 1 + const mockUser = { + id_utilisateur,email,photo_temporaire,confirmed,confirm_token + } + mockDocumentRepository.deleteFile = jest.fn(()=>{}) + mockUserRepository.updateUser = jest.fn((user)=>user) + afterEach(()=>{ + jest.clearAllMocks(); + }) + it("should return user with replaced photo ", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) + mockUserRepository.getByConfirmToken = jest.fn(()=>{ + return {...mockUser} + }) + const user = await completeAccount(pseudo,alias,bio,password,photo,confirm_token,serviceLocator) + expect(user.id_utilisateur).toBe(1) + expect(user.pseudo).toBe(pseudo) + expect(user.email).toBe(email) + expect(user.alias).toBe(alias) + expect(user.photo).toBe(photo) + expect(user.photo_temporaire).toBe(photo) + expect(user.confirm_token).toBe(null) + expect(user.confirmed).toBe(true) + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1) + }) + it("should return user without replaced photo ", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) + mockUserRepository.getByConfirmToken = jest.fn(()=>{ + return {...mockUser} + }) + const user = await completeAccount(pseudo,alias,bio,password,null,confirm_token,serviceLocator) + console.log(user) + + expect(user.id_utilisateur).toBe(1) + expect(user.pseudo).toBe(pseudo) + expect(user.email).toBe(email) + expect(user.alias).toBe(alias) + expect(user.photo_temporaire).toBe(photo_temporaire) + expect(user.confirm_token).toBe(null) + expect(user.confirmed).toBe(true) + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0) + }) + it("should return user without alias", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) + mockUserRepository.getByConfirmToken = jest.fn(()=>{ + return {...mockUser} + }) + const user = await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) + expect(user.id_utilisateur).toBe(1) + expect(user.pseudo).toBe(pseudo) + expect(user.email).toBe(email) + expect(user.alias).toBe(pseudo) + expect(user.photo_temporaire).toBe(photo_temporaire) + expect(user.confirm_token).toBe(null) + expect(user.confirmed).toBe(true) + }) + it("should throw error 403 user already exists", async ()=>{ + mockUserRepository.getByConfirmToken = jest.fn(()=>{ + return {...mockUser} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>'something') + const error = await catchError(async ()=>{ + await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should throw error 400 invalid token", async ()=>{ + mockUserRepository.getByConfirmToken = jest.fn(()=>null) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) + const error = await catchError(async ()=>{ + await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) + }) + expect(error.code).toBe(400) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/createUser.test.js b/test/unit/application/usecase/user/createUser.test.js new file mode 100644 index 0000000..c55f527 --- /dev/null +++ b/test/unit/application/usecase/user/createUser.test.js @@ -0,0 +1,74 @@ +const createUser = require('../../../../../lib/application/use_cases/user/CreateUser') +const catchError = require("../utils/catchError") +const mockUserRepository = {} +const mockSpotifyRepository = {} +const mockMailRepository = {} +const email = "testemail@gmail.com" +const display_name = "display_name" +const image = [{url:"testurl"}] +describe("createUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) + mockUserRepository.persist = jest.fn((user) => { + user.id_utilisateur = 1 + return user + }) + mockMailRepository.send = jest.fn(option => null) + mockSpotifyRepository.getToken= jest.fn(()=> { + return {access_token: 1, refresh_token: 1} + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return { + email, + display_name, + image + } + }) + const serviceLocator = { + userRepository: mockUserRepository, + spotifyRepository: mockSpotifyRepository, + mailRepository: mockMailRepository + } + it('should return user with email inscription', async () => { + + const user = await createUser(email,null,serviceLocator) + expect(user.email).toBe(email) + expect(user.confirmed).toBe(false) + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) + }); + it('should return user with spotify inscription', async () => { + const user = await createUser(null,"code",serviceLocator) + expect(user.email).toBe(email) + expect(user.alias).toBe(display_name) + expect(user.photo_temporaire).toBe('testurl') + expect(user.token).toBe(1) + expect(user.refresh_token).toBe(1) + expect(user.confirmed).toBe(false) + + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(1) + }); + it('should throw error 400 invalid code', async () => { + mockSpotifyRepository.getToken= jest.fn(()=> { + return {error: {}} + }) + const error = await catchError(async ()=>{ + await createUser(null,"code",serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) + }); + it('should throw error 403 email already exists', async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> 'something') + const error = await catchError(async ()=>{ + await createUser(email,null,serviceLocator) + }) + expect(error.code).toBe(403); + expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) + expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) + }); +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/getAccessToken.test.js b/test/unit/application/usecase/user/getAccessToken.test.js new file mode 100644 index 0000000..35f6a7b --- /dev/null +++ b/test/unit/application/usecase/user/getAccessToken.test.js @@ -0,0 +1,90 @@ +const UserRepository = require('../../../../../lib/infrastructure/repositories/interfaces/UserRepositoryAbstract'); +const getAccessToken = require('../../../../../lib/application/use_cases/security/GetAccessToken') +const bcrypt = require("bcrypt"); +const catchError = require("../utils/catchError") + + + +describe('getAccessToken', () =>{ + it('should generate access token', async () =>{ + const passwordTest = 'passwordTest' + const persistedUserCrypted = { + id_utilisateur:"id", + pseudo:"pseudo", + email:"email", + alias:"alias", + bio:"bio", + photo:"path/to/file", + photo_temporaire:"path/to/file", + password:await bcrypt.hash(passwordTest,10), + token:"token", + id_role:1, + ban_until:new Date("10-06-2003"), + } + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockAccessTokenManager.generate = jest.fn((uid) => 1) + mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) + expect( + await getAccessToken( + persistedUserCrypted.pseudo, + passwordTest, + { + userRepository : mockUserRepository, + accessTokenManager: mockAccessTokenManager + } + ) + ).toBe(1) + }) + + + it('should throw an error because the password is incorrect', async () =>{ + const passwordTest = 'passwordTest' + const persistedUserCrypted = { + id_utilisateur:"id", + pseudo:"pseudo", + email:"email", + alias:"alias", + bio:"bio", + photo:"path/to/file", + photo_temporaire:"path/to/file", + password:await bcrypt.hash(passwordTest,10), + token:"token", + id_role:1, + ban_until:new Date("10-06-2003"), + } + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) + const error = await catchError(async ()=>{ + await getAccessToken( + persistedUserCrypted.pseudo, + 'badPassword', + { + userRepository : mockUserRepository, + accessTokenManager: mockAccessTokenManager + } + ) + }) + + expect(error.code).toBe(401) + }) + + it('should throw an error because the user doesnt exists', async () =>{ + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockUserRepository.getByIdent = jest.fn((ident) => null) + const error = await catchError(async ()=> { + await getAccessToken( + '', + '', + { + userRepository: mockUserRepository, + accessTokenManager: mockAccessTokenManager + }) + } + ) + console.log(error) + expect(error.code).toBe(401) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/getUserByConfirmToken.test.js b/test/unit/application/usecase/user/getUserByConfirmToken.test.js new file mode 100644 index 0000000..a28a978 --- /dev/null +++ b/test/unit/application/usecase/user/getUserByConfirmToken.test.js @@ -0,0 +1,44 @@ +const getUserByConfirmToken = require('../../../../../lib/application/use_cases/user/getUserByConfirmToken') +const mockUser = { + id_utilisateur:1, + pseudo:'pseudo', + alias:'alias', + email:'testemail@gmail.com', + photo:'path/to/file', + photo_temporaire: 'path/to/file', + token:'token', + refresh_token:'refreshToken', + password:'password', + id_role:1, + ban_until:null, + confirmed:false, + confirm_token:'fezfezgezrhgez', + type:'user', +} +const expectedUser = { + pseudo:'pseudo', + alias:'alias', + email:'testemail@gmail.com', + photo:'path/to/file', + photo_temporaire: 'path/to/file', + id_role:1, + ban_until:null, + type:'user', +} +const mockUserRepository = {} +describe('getUserByPseudo',()=>{ + afterEach(async () => { + jest.clearAllMocks(); + }); + it('should return Public User',async ()=>{ + console.log("test") + mockUserRepository.getUserByConfirmToken = jest.fn(()=>mockUser) + const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) + expect(result).toEqual(expectedUser) + }) + it('should return null',async ()=>{ + mockUserRepository.getUserByConfirmToken = jest.fn(()=>null) + const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) + expect(result).toBeNull() + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/getUserByPseudo.test.js b/test/unit/application/usecase/user/getUserByPseudo.test.js new file mode 100644 index 0000000..4da6124 --- /dev/null +++ b/test/unit/application/usecase/user/getUserByPseudo.test.js @@ -0,0 +1,44 @@ +const getUserBuPseudo = require('../../../../../lib/application/use_cases/user/getUserByPseudo') +const mockUser = { + id_utilisateur:1, + pseudo:'pseudo', + alias:'alias', + email:'testemail@gmail.com', + photo:'path/to/file', + photo_temporaire: 'path/to/file', + token:'token', + refresh_token:'refreshToken', + password:'password', + id_role:1, + ban_until:null, + confirmed:false, + confirm_token:'fezfezgezrhgez', + type:'user', +} +const expectedUser = { + pseudo:'pseudo', + alias:'alias', + email:'testemail@gmail.com', + photo:'path/to/file', + photo_temporaire: 'path/to/file', + id_role:1, + ban_until:null, + type:'user', +} +const mockUserRepository = {} +describe('getUserByPseudo',()=>{ + afterEach(async () => { + jest.clearAllMocks(); + }); + it('should return Public User',async ()=>{ + console.log("test") + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>mockUser) + const result = await getUserBuPseudo("test",{userRepository:mockUserRepository}) + expect(result).toEqual(expectedUser) + }) + it('should return null',async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) + const result = await getUserBuPseudo("test",{userRepository:mockUserRepository}) + expect(result).toBeNull() + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/uploadPreview.test.js b/test/unit/application/usecase/user/uploadPreview.test.js index b8ea762..72dae52 100644 --- a/test/unit/application/usecase/user/uploadPreview.test.js +++ b/test/unit/application/usecase/user/uploadPreview.test.js @@ -3,6 +3,7 @@ mockUserRepository = {} mockDocumentRepository = {} mockID = 12424 const uploadPreview = require("../../../../../lib/application/use_cases/user/uploadPreview") +const catchError = require("../utils/catchError"); const mockFile = { hapi: { filename: "test.png", @@ -53,27 +54,37 @@ describe("uploadPreview usecase", ()=>{ _data: "testDFata" } it('should throw 415 error',async() =>{ - await expect(uploadPreview(invalidMockFile,'token', { - accessTokenManager: mockAccessTokenManager, - userRepository: mockUserRepository, - documentRepository: mockDocumentRepository - })).rejects.toThrow("le fichier fourni n'est pas une image") + const error = await catchError(async ()=>{ + await uploadPreview(invalidMockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + }) + }) + expect(error.code).toBe(415) } ) it('should throw 500 error',async() =>{ mockDocumentRepository.uploadFile = jest.fn((path,file) => null) - await expect(uploadPreview(mockFile,'token', { - accessTokenManager: mockAccessTokenManager, - userRepository: mockUserRepository, - documentRepository: mockDocumentRepository - })).rejects.toThrow("internal server error") + const error = await catchError(async ()=>{ + await uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + }) + }) + expect(error.code).toBe(500) + } ) it('should throw 401 error',async() =>{ mockUserRepository.getByUser = jest.fn((id) => null) - await expect(uploadPreview(mockFile,'token', { - accessTokenManager: mockAccessTokenManager, - userRepository: mockUserRepository, - documentRepository: mockDocumentRepository - })).rejects.toThrow("votre token d'authentification n'est pas le bon") + const error = await catchError(async ()=>{ + await uploadPreview(mockFile,'token', { + accessTokenManager: mockAccessTokenManager, + userRepository: mockUserRepository, + documentRepository: mockDocumentRepository + }) + }) + expect(error.code).toBe(401) } ) }) diff --git a/test/unit/application/usecase/user/user.test.js b/test/unit/application/usecase/user/user.test.js deleted file mode 100644 index 9187891..0000000 --- a/test/unit/application/usecase/user/user.test.js +++ /dev/null @@ -1,163 +0,0 @@ - -const createUser = require('../../../../../lib/application/use_cases/user/CreateUser') -const UserRepository = require('../../../../../lib/infrastructure/repositories/interfaces/UserRepositoryAbstract'); -const getAccessToken = require('../../../../../lib/application/use_cases/security/GetAccessToken') -const User = require('../../../../../lib/domain/model/User') -const bcrypt = require("bcrypt"); -const {use} = require("bcrypt/promises"); -const persistedUser = new User( - 1, - 'testPeudo', - 'testEmail@gmail.com', - 'test_alias', - 'testbio', - 'path/to/file', - 'path/to/file', - 'passwordtest', - 'spotifyToken', - 'refreshToken', - 2, - new Date("10-06-2003") -) - - -describe('createUser', () =>{ - it("should create an user", async () =>{ - const mockUserRepository = new UserRepository(); - mockUserRepository.persist = jest.fn(() => persistedUser) - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => null) - const user = await createUser( - 'testPeudo', - 'testEmail@gmail.com', - 'test_alias', - 'testbio', - 'passwordtest', - 'spotifyToken', - 'refreshToken', - {userRepository : mockUserRepository} - ) - const mockResult = mockUserRepository.persist.mock.calls[0][0] - expect(mockUserRepository.persist).toHaveBeenCalled() - expect(mockResult.id).toBe(null) - expect(mockResult.pseudo).toBe(persistedUser.pseudo) - expect(mockResult.email).toBe(persistedUser.email) - expect(mockResult.alias).toBe(persistedUser.alias) - expect(mockResult.bio).toBe(persistedUser.bio) - expect(mockResult.token).toBe(persistedUser.token) - expect(mockResult.refresh_token).toBe(persistedUser.refresh_token) - expect(mockResult.id_role).toBe(2) - expect(mockResult.photo).toBe(null) - expect(mockResult.tempPhoto).toBe(null) - expect(mockResult.banUntil).toBe(null) - expect(mockResult.password).not.toBe(user.password) - }) - it('should throw an error when an user with the same pseudo exists', async ()=>{ - const mockUserRepository = new UserRepository(); - mockUserRepository.persist = jest.fn(() => persistedUser) - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => persistedUser) - await expect(createUser( - persistedUser.pseudo, - '', - '', - '', - '', - '', - '', - {userRepository: mockUserRepository} - )).rejects.toThrow('Email ou Pseudo déjà existant') - }) - it('should throw an error when an user with the same email exists', async ()=>{ - const mockUserRepository = new UserRepository(); - mockUserRepository.persist = jest.fn(() => persistedUser) - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo) => persistedUser) - await expect(createUser( - '', - persistedUser.email, - '', - '', - '', - '', - '', - {userRepository: mockUserRepository} - )).rejects.toThrow('Email ou Pseudo déjà existant') - }) - -}) - -describe('getAccessToken', () =>{ - it('should generate access token', async () =>{ - const passwordTest = 'passwordTest' - const persistedUserCrypted = new User( - 1, - 'testPeudo', - 'testEmail@gmail.com', - 'test_alias', - 'testbio', - 'path/to/file', - 'path/to/file', - await bcrypt.hash(passwordTest,10), - 'spotifyToken', - 2, - new Date("10-06-2003")) - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockAccessTokenManager.generate = jest.fn((uid) => 1) - mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) - expect( - await getAccessToken( - persistedUserCrypted.pseudo, - passwordTest, - { - userRepository : mockUserRepository, - accessTokenManager: mockAccessTokenManager - } - ) - ).toBe(1) - }) - - - it('should throw an error because the password is incorrect', async () =>{ - const passwordTest = 'passwordTest' - const persistedUserCrypted = new User( - 1, - 'testPeudo', - 'testEmail@gmail.com', - 'test_alias', - 'testbio', - 'path/to/file', - 'path/to/file', - await bcrypt.hash(passwordTest,10), - 'spotifyToken', - 2, - new Date("10-06-2003")) - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) - await expect( - getAccessToken( - persistedUserCrypted.pseudo, - 'badPassword', - { - userRepository : mockUserRepository, - accessTokenManager: mockAccessTokenManager - } - ) - ).rejects.toThrow('Bad credentials') - }) - - it('should throw an error because the user doesnt exists', async () =>{ - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockUserRepository.getByIdent = jest.fn((ident) => null) - await expect( - getAccessToken( - '', - '', - { - userRepository : mockUserRepository, - accessTokenManager: mockAccessTokenManager - } - ) - ).rejects.toThrow('Bad credentials') - }) -}) \ No newline at end of file diff --git a/test/unit/application/usecase/utils/catchError.js b/test/unit/application/usecase/utils/catchError.js new file mode 100644 index 0000000..af929d4 --- /dev/null +++ b/test/unit/application/usecase/utils/catchError.js @@ -0,0 +1,9 @@ +module.exports = async (action) => { + try{ + await action() + }catch (error){ + return error + } + console.log("dfezfezfzefez") + return null +} \ No newline at end of file diff --git a/test/unit/infrastructure/repositories/DocumentRepository.test.js b/test/unit/infrastructure/repositories/DocumentRepository.test.js index f2cff32..d510cb3 100644 --- a/test/unit/infrastructure/repositories/DocumentRepository.test.js +++ b/test/unit/infrastructure/repositories/DocumentRepository.test.js @@ -1,5 +1,5 @@ const DocumentRepositoryTest = require("../../../../lib/infrastructure/repositories/DocumentRepository") -const createUser = require("../../../../lib/application/use_cases/user/CreateUser"); +const createUser = require("../../../../lib/application/use_cases/user/CompleteAccount"); let filePath const documentRepository = new DocumentRepositoryTest(); const mockFile = { From 3eade89be6715f023444e5a9f502ec13db303521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Fri, 29 Dec 2023 22:35:14 +0100 Subject: [PATCH 11/67] correction --- lib/infrastructure/repositories/UserRepository.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index a1acdfb..11c245b 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -86,7 +86,7 @@ module.exports = class extends userRepository { return updatedRowsCount } async updateUser(user){ - const { pseudo, email, password,alias, bio, id_role,ban_until,token,refresh_token,confirmed,confirmToken,photo, photo_temporaire } = user; + const { pseudo, email, password,alias, bio, id_role,ban_until,token,refresh_token,confirmed,confirm_token,photo, photo_temporaire } = user; const seqUser = await this.model.findOne({ where: { id_utilisateur : user.id_utilisateur @@ -102,7 +102,7 @@ module.exports = class extends userRepository { seqUser.id_role = id_role seqUser.ban_until = ban_until seqUser.confirmed = confirmed - seqUser.confirm_token = confirmToken + seqUser.confirm_token = confirm_token seqUser.photo = photo seqUser.photo_temporaire = photo_temporaire seqUser.save() From 6a94766f918e4f5f17c7ad1c394313e82259d551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:49:31 +0100 Subject: [PATCH 12/67] coverage feature (#18) --- .gitignore | 1 + lib/application/use_cases/image/uploadImage.js | 16 ---------------- .../use_cases/security/VerifyAccessToken.js | 9 --------- package.json | 10 +++------- 4 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 lib/application/use_cases/image/uploadImage.js delete mode 100644 lib/application/use_cases/security/VerifyAccessToken.js diff --git a/.gitignore b/.gitignore index 8bec7db..fdf69f3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules upload +coverage diff --git a/lib/application/use_cases/image/uploadImage.js b/lib/application/use_cases/image/uploadImage.js deleted file mode 100644 index 93d9d92..0000000 --- a/lib/application/use_cases/image/uploadImage.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; -const crypto = require('crypto'); -const {writeFile} = require("fs"); -module.exports = async (image) => { - return new Promise((resolve, reject) => { - const fileLabel = crypto.randomBytes(16).toString('hex') - const fileExt = image.hapi.filename.split(".")[1] - const path = `upload/${fileLabel}.${fileExt}` - writeFile(`./${path}`, image._data, err => { - if (err) { - reject(err) - } - resolve({ path }) - }) - }) -}; diff --git a/lib/application/use_cases/security/VerifyAccessToken.js b/lib/application/use_cases/security/VerifyAccessToken.js deleted file mode 100644 index c33f20b..0000000 --- a/lib/application/use_cases/security/VerifyAccessToken.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -module.exports = (accessToken, { accessTokenManager }) => { - const decoded = accessTokenManager.decode(accessToken); - if (!decoded) { - throw new Error('Invalid access token'); - } - return { uid: decoded.uid }; -}; diff --git a/package.json b/package.json index 25d9699..412bd56 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,8 @@ "npm": ">=6.12" }, "jest": { - "collectCoverageFrom": [ - "**/*.{js}", - "!**/node_modules/**", - "!**/vendor/**", - "!**/coverage/**" - ], - "testURL": "http://localhost/" + "collectCoverage":true, + "collectCoverageFrom":["lib/application/**/*.js"], + "coverageDirectory":"coverage" } } From 1968a864e8e6aa2ee42f8a0e6fbd7230ecece90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:41:38 +0100 Subject: [PATCH 13/67] Feature/fetch artiste (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commentaires * création fectch artiste (test) * recuperation infos d'un artiste via son ID * test fetchArtist * tests integration de fetchArtist * tests integration de fetchArtist modif --------- Co-authored-by: Léa Thai --- .../use_cases/spotify/FetchArtist.js | 15 +++++++ lib/application/use_cases/spotify/Search.js | 5 +++ lib/domain/entity/SpotifyEntity.js | 10 +++-- .../repositories/SpotifyRepository.js | 12 +++++ .../interfaces/SpotifyRepositoryAbstract.js | 3 ++ .../controllers/SpotifyController.js | 17 ++++++- lib/interfaces/routes/spotify.js | 44 +++++++++++++++---- test/integration/spotify.test.js | 20 ++++++++- .../usecase/fixtures/fetchArtist.js | 12 +++++ .../usecase/spotify/FetchArtist.test.js | 22 ++++++++++ 10 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 lib/application/use_cases/spotify/FetchArtist.js create mode 100644 test/unit/application/usecase/fixtures/fetchArtist.js create mode 100644 test/unit/application/usecase/spotify/FetchArtist.test.js diff --git a/lib/application/use_cases/spotify/FetchArtist.js b/lib/application/use_cases/spotify/FetchArtist.js new file mode 100644 index 0000000..6ff1279 --- /dev/null +++ b/lib/application/use_cases/spotify/FetchArtist.js @@ -0,0 +1,15 @@ +const SerializeArtist = require("../../../interfaces/serializers/ArtistSerializer") +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (id, {spotifyRepository}) =>{ +let artistInfo = {} + try{ + artistInfo = await spotifyRepository.getSpotifyArtist(id) + } + catch(error){ + throwStatusCode(400, `l'id : ${id} n'existe pas`) + } + + const artist = SerializeArtist(artistInfo) // Formatage de l'objet + return artist +} \ No newline at end of file diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index e1e8b70..e006737 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -12,12 +12,17 @@ module.exports = async (query,filter, limit,allow_user, {spotifyRepository, user } const searchListRaw = await spotifyRepository.getSpotifySearchList(query, filter, limitSize) let returnValue = [] + + // Formatage des objets const tracks = searchListRaw?.tracks ? searchListRaw?.tracks?.items.map(item => SerializeTrack(item)) : [] + const albums = searchListRaw?.albums ? searchListRaw?.albums?.items.map(item => SerializeAlbum(item)) : [] + const artists = searchListRaw?.artists ? searchListRaw?.artists?.items.map(item => SerializeArtist(item)) : [] + returnValue.push(...tracks) returnValue.push(...albums) returnValue.push(...artists) diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index cab88d2..198ca17 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -1,3 +1,4 @@ +// Verifie le bon format de la query / payload const Joi = require('@hapi/joi') const trackBody = Joi.object().keys({ query: Joi @@ -7,9 +8,8 @@ const trackBody = Joi.object().keys({ .required(), spotify_filter: Joi .string() - .required() .max(50) - .required() + .optional() .custom((value, helpers) =>{ const allowedValues = ["track","artist","album"] const tabValue = value.split(",") @@ -25,5 +25,7 @@ const trackBody = Joi.object().keys({ limit: Joi.number().integer().required() }) - -module.exports = {trackBody} \ No newline at end of file +const fetchArtist = Joi.object().keys({ + query: Joi.string().min(1).required(), //correspond au ID Artist +}) +module.exports = {trackBody,fetchArtist} \ No newline at end of file diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index 30c9567..37c1a80 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -26,6 +26,7 @@ module.exports = class extends spotifyRepositoryAbstract{ return response.data.access_token }) } + async getSpotifySearchList(query,filter,limit){ return axios.get('https://api.spotify.com/v1/search', { headers: { @@ -42,6 +43,17 @@ module.exports = class extends spotifyRepositoryAbstract{ }) } + async getSpotifyArtist(id){ + return axios.get(`https://api.spotify.com/v1/artists/${id}`, { + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, + }) + .then((response) => { + return response.data + }) + } + refreshToken(token) { const payload = new URLSearchParams() payload.append('grant_type', 'refresh_token'); diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 572c7c1..6b7d59d 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -19,6 +19,9 @@ module.exports = class { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + async getSpotifyArtist(id){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index 62a6f45..247a8d4 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -1,14 +1,16 @@ const search = require("../../application/use_cases/spotify/Search"); const getAuthURL = require("../../application/use_cases/spotify/GetAuthURL"); +const fetchArtist = require("../../application/use_cases/spotify/FetchArtist"); const handleError = require("./utils/handleError"); + module.exports = { async search(request,handler){ try{ // Context - const serviceLocator = request.server.app.serviceLocator; + const serviceLocator = request.server.app.serviceLocator; // a tous les repo // Input - + // spotify_filter = type de l'API Spotify let { query, spotify_filter, limit, allow_user} = request.query const searchResult = await search(query,spotify_filter, limit,allow_user, serviceLocator) return handler.response(searchResult).code(200) @@ -16,6 +18,17 @@ module.exports = { return handleError(error) } }, + + async fetchArtist(request,handler){ + try { + const serviceLocator = request.server.app.serviceLocator; + const id = request.query.query + const fetchArtistResult = await fetchArtist(id, serviceLocator) + return handler.response(fetchArtistResult).code(200) // tout s est bien passe + }catch(error){ + return handleError(error) + } + }, async getAuthURL(request,handler) { try{ const authURL = await getAuthURL() diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index 886ff5b..dfe7087 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -1,4 +1,4 @@ -const {trackBody} = require("../../domain/entity/SpotifyEntity") +const { trackBody,fetchArtist } = require("../../domain/entity/SpotifyEntity") const spotify = require("../controllers/SpotifyController") module.exports = { name: 'spotify', @@ -16,14 +16,14 @@ module.exports = { plugins: { 'hapi-swagger': { responses: { - 200: {description : 'Success'}, - 204: {description : 'No content'}, - 401: {description : 'Unauthorized'}, - 403: {description : 'forbidden'}, - 404: {description : 'Ressource not found'}, - 500: {description : 'Internal server error'}, - 502: {description : 'bad gateway'}, - 503: {description : 'Service unavailable'}, + 200: { description: 'Success' }, + 204: { description: 'No content' }, + 401: { description: 'Unauthorized' }, + 403: { description: 'forbidden' }, + 404: { description: 'Ressource not found' }, + 500: { description: 'Internal server error' }, + 502: { description: 'bad gateway' }, + 503: { description: 'Service unavailable' }, } } }, @@ -32,6 +32,32 @@ module.exports = { } }, }, + { + method: 'GET', + path: '/spotify/fetchArtist', + handler: spotify.fetchArtist, + options: { + description: 'get Artist informations', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: { description: 'Success' }, + 204: { description: 'No content' }, + 401: { description: 'Unauthorized' }, + 403: { description: 'forbidden' }, + 404: { description: 'Ressource not found' }, + 500: { description: 'Internal server error' }, + 502: { description: 'bad gateway' }, + 503: { description: 'Service unavailable' }, + } + } + }, + validate: { + query: fetchArtist + } + }, + }, { method: 'GET', path: '/spotify/getAuthURL', diff --git a/test/integration/spotify.test.js b/test/integration/spotify.test.js index 53394cf..bdee86d 100644 --- a/test/integration/spotify.test.js +++ b/test/integration/spotify.test.js @@ -7,6 +7,7 @@ const mockUserRepository = {} const mockSpotifyRepository = {} mockUserRepository.getUsersByPseudo = jest.fn((pseudo) =>{return []}) mockSpotifyRepository.getSpotifySearchList = jest.fn((query, filter, limitSize) =>{return {}}) +mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{return {}}) // mock la fct de repo describe('spotify route', () => { @@ -84,6 +85,23 @@ describe('spotify route', () => { expect(res1.statusCode).toBe(200); } }); - }) + }), + + describe("/spotify/FetchArtist", ()=>{ + it('should respond code 400 invalid query/id', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=`, + }); + expect(res1.statusCode).toBe(400); + }); + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=query`, + }); + expect(res1.statusCode).toBe(200); + }); + }) }); diff --git a/test/unit/application/usecase/fixtures/fetchArtist.js b/test/unit/application/usecase/fixtures/fetchArtist.js new file mode 100644 index 0000000..e5a2891 --- /dev/null +++ b/test/unit/application/usecase/fixtures/fetchArtist.js @@ -0,0 +1,12 @@ +const { + artistFixture, + expectedFixture +} = require("../../../interfaces/serializers/fixtures/artistFixture") + +artistFixture.popularity = 2 +expectedFixture.popularity = 2 + +module.exports = { + artistFixture, + expectedFixture, +} \ No newline at end of file diff --git a/test/unit/application/usecase/spotify/FetchArtist.test.js b/test/unit/application/usecase/spotify/FetchArtist.test.js new file mode 100644 index 0000000..4214ca1 --- /dev/null +++ b/test/unit/application/usecase/spotify/FetchArtist.test.js @@ -0,0 +1,22 @@ +const fetchArtist = require("../../../../../lib/application/use_cases/spotify/FetchArtist") +const { + artistFixture, // reponse pas serialise + expectedFixture, // reponse serialise +} = require("../fixtures/fetchArtist") + +const mockSpotifyRepository = {} +mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{ + return artistFixture +}) + +const idOrelsan = "4FpJcNgOvIpSBeJgRg3OfN" + +describe('FetchArtist usecase', () => { + it("should return a serialized artist item", async ()=>{ + const result = await fetchArtist( // fetchArtist metier, serialize est fait a la fin + idOrelsan, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtist de mockSpotifyRepository + expect(result).toEqual(expectedFixture) + expect(mockSpotifyRepository.getSpotifyArtist).toHaveBeenCalledWith(idOrelsan) + }) +}); \ No newline at end of file From 4f049c2f10f9f0272bed6a89be8fed79e4167528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Sun, 7 Jan 2024 13:33:34 +0100 Subject: [PATCH 14/67] save (#19) * save * file deletion --- lib/application/use_cases/user/CreateUser.js | 6 +-- .../use_cases/user/resetPassword.js | 20 ++++++++ .../use_cases/user/sendResetEmail.js | 22 +++++++++ .../use_cases/utils/throwStatusCode.js | 3 +- lib/domain/model/User.js | 1 + .../orm/sequelize/models/Utilisateur.js | 3 ++ .../repositories/UserRepository.js | 33 +++++++------ .../interfaces/UserRepositoryAbstract.js | 8 ++- .../routine/removeExpiredConfirmTokens.js | 1 + lib/interfaces/controllers/UsersController.js | 26 +++++++++- lib/interfaces/routes/users.js | 49 ++++++++++++++++++- 11 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 lib/application/use_cases/user/resetPassword.js create mode 100644 lib/application/use_cases/user/sendResetEmail.js diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index feaf7c1..102d3ea 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -1,8 +1,6 @@ 'use strict'; const User = require('../../../domain/model/User'); -const bcrypt = require("bcrypt"); -const rolesEnum = require('../../../domain/model/utils/RolesEnum') const throwStatusCode = require("../utils/throwStatusCode") const crypto = require("crypto"); module.exports = async (email,spotify_code, { userRepository,mailRepository,spotifyRepository}) => { @@ -10,7 +8,7 @@ module.exports = async (email,spotify_code, { userRepository,mailRepository,spot if(spotify_code){ ({access_token, refresh_token,error} = await spotifyRepository.getToken(spotify_code)) if(error){ - throwStatusCode(400,error) + throwStatusCode(400,error.message) } ({email,display_name,image} = await spotifyRepository.getAccountData(access_token)) image = image?.at(-1)?.url @@ -43,6 +41,6 @@ module.exports = async (email,spotify_code, { userRepository,mailRepository,spot setTimeout(()=>{ userRepository.removeUserByConfirmToken(confirm_token) - },1000*60*24) + },3600*1000*24) return user }; diff --git a/lib/application/use_cases/user/resetPassword.js b/lib/application/use_cases/user/resetPassword.js new file mode 100644 index 0000000..0eae0bb --- /dev/null +++ b/lib/application/use_cases/user/resetPassword.js @@ -0,0 +1,20 @@ +'use strict'; + +const User = require('../../../domain/model/User'); +const bcrypt = require("bcrypt"); +const rolesEnum = require('../../../domain/model/utils/RolesEnum') +const throwStatusCode = require("../utils/throwStatusCode") +const crypto = require("crypto"); +module.exports = async (password,resetToken,{userRepository}) => { + const user = await userRepository.getByResetToken(resetToken) + if(!user) { + throwStatusCode(400,'Token invalide') + } + password = await bcrypt.hash(password,10) + if(!password) { + throwStatusCode('500','Internal server error') + } + user.reset_token = null + user.password = password + await userRepository.updateUser(user) +}; diff --git a/lib/application/use_cases/user/sendResetEmail.js b/lib/application/use_cases/user/sendResetEmail.js new file mode 100644 index 0000000..7f2cd15 --- /dev/null +++ b/lib/application/use_cases/user/sendResetEmail.js @@ -0,0 +1,22 @@ +'use strict'; +const crypto = require("crypto"); +module.exports = async (email,{userRepository ,mailRepository}) => { + const user = await userRepository.getByEmailOrPseudo(email,email) + if(!user || !user.confirmed) return false + const reset_token = crypto.randomBytes(16).toString('hex') + user.reset_token = reset_token + await userRepository.updateUser(user) + const mailOptions = { + from: process.env.MAILER_EMAIL, + to: email, + subject: 'reinitialisation de votre mot de passe', + text: reset_token + }; + await mailRepository.send(mailOptions) + + setTimeout(()=>{ + delete user.reset_token + userRepository.updateUser(user) + },3600*1000) + return true +}; diff --git a/lib/application/use_cases/utils/throwStatusCode.js b/lib/application/use_cases/utils/throwStatusCode.js index c732135..e2eb250 100644 --- a/lib/application/use_cases/utils/throwStatusCode.js +++ b/lib/application/use_cases/utils/throwStatusCode.js @@ -1,5 +1,6 @@ module.exports = (code, message) => { - const error = new Error(message) + const error = new Error(message || 'error') + console.log(error) error.code = code throw error } \ No newline at end of file diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index d1c2768..61e7ae6 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -11,6 +11,7 @@ module.exports = class { this.photo_temporaire = userRaw?.photo_temporaire this.token = userRaw?.token this.refresh_token = userRaw?.refresh_token + this.reset_token = userRaw?.reset_token this.password = userRaw?.password; this.id_role = userRaw?.id_role; this.ban_until = userRaw?.ban_until diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index fcb10bf..3738d11 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -53,6 +53,9 @@ module.exports = (sequelize) => { }, confirm_token: { type: DataTypes.STRING(50), + }, + reset_token: { + type: DataTypes.STRING(50), }, }, { freezeTableName: true,} diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index 11c245b..8320733 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -86,25 +86,17 @@ module.exports = class extends userRepository { return updatedRowsCount } async updateUser(user){ - const { pseudo, email, password,alias, bio, id_role,ban_until,token,refresh_token,confirmed,confirm_token,photo, photo_temporaire } = user; + const { pseudo, email, password,alias, bio, id_role,ban_until,token,refresh_token,confirmed,confirm_token,photo, photo_temporaire, reset_token} = user; const seqUser = await this.model.findOne({ where: { id_utilisateur : user.id_utilisateur } }); - seqUser.pseudo = pseudo - seqUser.email = email - seqUser.alias = alias - seqUser.bio = bio - seqUser.password = password - seqUser.token = token - seqUser.refresh_token = refresh_token - seqUser.id_role = id_role - seqUser.ban_until = ban_until - seqUser.confirmed = confirmed - seqUser.confirm_token = confirm_token - seqUser.photo = photo - seqUser.photo_temporaire = photo_temporaire + for (let attribut in user) { + if (user.hasOwnProperty(attribut)) { + seqUser[attribut] = user[attribut]; + } + } seqUser.save() } async getSpotifyAuthUser(){ @@ -148,5 +140,18 @@ module.exports = class extends userRepository { }); return this.createUser(seqUsers) } + clearResetTokens() { + this.model.update({ + reset_token: null + },{ where: {reset_token: {[Op.not]: null}} }) + } + async getByResetToken(resetToken) { + const seqUsers = await this.model.findOne({ + where: { + reset_token: resetToken + } + }); + return this.createUser(seqUsers) + } }; diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index d09e58b..98c3d1b 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -40,6 +40,10 @@ module.exports = class { getByConfirmToken(token) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - - + clearResetTokens() { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getByResetToken(resetToken) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/routine/removeExpiredConfirmTokens.js b/lib/infrastructure/routine/removeExpiredConfirmTokens.js index 65e774c..860b7fa 100644 --- a/lib/infrastructure/routine/removeExpiredConfirmTokens.js +++ b/lib/infrastructure/routine/removeExpiredConfirmTokens.js @@ -1,3 +1,4 @@ module.exports = async ({ userRepository}) => { userRepository.removeUncheckedAccounts() + userRepository.clearResetTokens() } \ No newline at end of file diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index e74c2f6..ca7f65f 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -7,6 +7,8 @@ const handleError = require("./utils/handleError") const uploadPreview = require("../../application/use_cases/user/uploadPreview") const getUserByPseudo = require("../../application/use_cases/user/getUserByPseudo") const getUserByConfirmToken = require("../../application/use_cases/user/getUserByConfirmToken") +const sendResetEmail = require("../../application/use_cases/user/sendResetEmail") +const resetPassword = require("../../application/use_cases/user/resetPassword") const refreshToken = require("../../application/use_cases/spotify/RefreshToken") const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); module.exports = { @@ -88,7 +90,29 @@ module.exports = { throwStatusCode(403,"no user") return handler.response(user).code(200) } - catch (e){ + catch (e){ + return handleError(e) + } + }, + async sendResetEmail(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const {email} = request.payload + try{ + await sendResetEmail(email,serviceLocator); + return handler.response("un email a été envoyé").code(200) + } + catch (e){ + return handleError(e) + } + }, + async resetPassword(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const {password,resetToken} = request.payload + try{ + await resetPassword(password,resetToken,serviceLocator); + return handler.response("un email a été envoyé").code(200) + } + catch (e){ return handleError(e) } } diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 388283f..656b997 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -2,6 +2,7 @@ const {userSignUp, userSignIn} = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); const Joi = require('joi') +const validationErrror = require("../../domain/entity/utils/validationError"); const MAX_BYTE_SIZE =20971520 module.exports = { name: 'users', @@ -170,7 +171,53 @@ module.exports = { }) } } - } + }, + { + method: 'POST', + path: '/users/sendResetEmail', + handler: UsersController.sendResetEmail, + options: { + description: 'send reset email', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 500: {description: 'Internal server error'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + email: Joi.string().email().min(10).max(40), + }) + } + } + }, + { + method: 'POST', + path: '/users/resetPassword', + handler: UsersController.resetPassword, + options: { + description: 'reset user password', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 403: {description : 'forbidden'}, + 500: {description: 'Internal server error'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + resetToken: Joi.string().max(50).required(), + password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")) + }) + } + } + }, ]); } }; \ No newline at end of file From 1d839cd2b7a8b6c5f048b51bc417a950f2445192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:23:09 +0100 Subject: [PATCH 15/67] Fix/mac compatibility (#20) * Update .gitignore file * Fix database URI and file path in server.js * Update .env git ignore errror --------- Co-authored-by: Alban22 --- .gitignore | 3 ++- lib/infrastructure/webserver/server.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fdf69f3..9f42944 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ node_modules upload -coverage +docker-compose.yml +.env \ No newline at end of file diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index c5ebbe7..43a87eb 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -19,7 +19,7 @@ const createServer = async () => { port: process.env.PORT || 3000, routes:{ files: { - relativeTo: Path.join(__dirname.split("\\").slice(0,-3).join("\\")+'\\upload') + relativeTo: Path.join(__dirname.split("\\").slice(0,-3).join("/")+'/upload') } } }); From e35b7a06674fb6e6d53f8828505a31c42eb500bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:18:51 +0100 Subject: [PATCH 16/67] test + cors (#21) --- .../use_cases/user/resetPassword.js | 1 + lib/domain/entity/UserEntity.js | 2 +- lib/infrastructure/webserver/server.js | 8 + package-lock.json | 3162 ++++++++++++++++- package.json | 11 +- test/integration/users.test.js | 73 +- .../usecase/user/resetPassword.test.js | 26 + .../usecase/user/sendResetEmail.test.js | 29 + 8 files changed, 3122 insertions(+), 190 deletions(-) create mode 100644 test/unit/application/usecase/user/resetPassword.test.js create mode 100644 test/unit/application/usecase/user/sendResetEmail.test.js diff --git a/lib/application/use_cases/user/resetPassword.js b/lib/application/use_cases/user/resetPassword.js index 0eae0bb..7fb6c64 100644 --- a/lib/application/use_cases/user/resetPassword.js +++ b/lib/application/use_cases/user/resetPassword.js @@ -17,4 +17,5 @@ module.exports = async (password,resetToken,{userRepository}) => { user.reset_token = null user.password = password await userRepository.updateUser(user) + return user }; diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index 32f6dfa..f6a1091 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -16,7 +16,7 @@ const userSignUp = Joi.object().keys({ password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")), photo: Joi.string().max(500), confirmToken: Joi.string().max(50), - bio:Joi.string().max(1500).error(validationErrror("bio","le pseudo doit faire moins de 1500 caractères")), + bio:Joi.string().min(0).max(1500).error(validationErrror("bio","la bio doit faire moins de 1500 caractères")), }) module.exports = {userSignIn, userSignUp} \ No newline at end of file diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 43a87eb..f4b5262 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -13,6 +13,7 @@ const strategy = require("../config/strategy") const Path = require("path"); const refreshTokens = require("../routine/refreshTokens") const removeExpiredConfirmTokens = require("../routine/removeExpiredConfirmTokens") +const HapiCors = require('hapi-cors'); const createServer = async () => { // Create a server with a host and port const server = Hapi.server({ @@ -61,6 +62,13 @@ const createServer = async () => { // }, { plugin: Jwt + }, + { + plugin: HapiCors, + options: { + origins: [process.env.FRONT_URL], // Specify the allowed origins + methods: ['GET', 'POST', 'PUT', 'DELETE'], // Specify the allowed HTTP methods + }, } ]); // for (const route in routes) { diff --git a/package-lock.json b/package-lock.json index 79b7f56..2d7c252 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,11 +23,14 @@ "bcrypt": "^5.1.1", "blipp": "^4.0.2", "dotenv": "^8.6.0", + "hapi-cors": "^1.0.3", "hapi-swagger": "^17.2.0", + "install": "^0.13.0", "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", "mysql2": "^3.6.0", "nodemailer": "^6.9.7", + "npm": "^10.2.5", "request": "^2.88.2", "require-directory": "^2.1.1", "sequelize": "^5.21.11", @@ -2852,8 +2855,7 @@ "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "node_modules/cjs-module-lexer": { "version": "1.2.3", @@ -3657,8 +3659,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/handlebars": { "version": "4.7.8", @@ -3680,6 +3681,30 @@ "uglify-js": "^3.1.4" } }, + "node_modules/hapi-cors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hapi-cors/-/hapi-cors-1.0.3.tgz", + "integrity": "sha512-45fkvy13d+Awp25OXuMj8imQSoD3x5SJ99D+P/WBEwruHArpoHdj+zMlrXOoixgo/O281/lls6rAZBnkBtNOpg==", + "deprecated": "This package is no longer being supported. Please migrate to the CORS functionality built into hapi.", + "dependencies": { + "joi": "^7.0.1" + } + }, + "node_modules/hapi-cors/node_modules/joi": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-7.3.0.tgz", + "integrity": "sha512-7ysLFfGtSg5L1MWnIkJGvJLAsdZPLZK2+qAi+0D9QdsnlPVSk+dBZxEl1ezveJuhEcvZKLK+AZSkmnXeATcB0A==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "dependencies": { + "hoek": "3.x.x", + "isemail": "2.x.x", + "moment": "2.x.x", + "topo": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/hapi-swagger": { "version": "17.2.0", "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-17.2.0.tgz", @@ -3734,7 +3759,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -3765,6 +3789,15 @@ "node": ">= 0.4" } }, + "node_modules/hoek": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-3.0.4.tgz", + "integrity": "sha512-VIMFzySNWnvVqBZIWJSHzun/dvtgYYxv0DypA8Mr9ue+kjXyf1mkq4/EOU/a33cIoW+fFyk9+t8W6ZSqucKYpA==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3889,7 +3922,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -3921,11 +3953,18 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "dev": true, "engines": { "node": "*" } }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4095,11 +4134,18 @@ "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "dev": true }, + "node_modules/isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha512-LPjFxaTatluwGAJlGe4FtRdzg0a9KlXrahHoHAR4HwRNf90Ttwi6sOQ9zj+EoCPmk9yyK+WFUqkm0imUo8UJbw==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isstream": { "version": "0.1.2", @@ -4970,8 +5016,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema": { "version": "0.2.3", @@ -5528,7 +5573,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, "dependencies": { "abbrev": "1" }, @@ -5557,6 +5601,164 @@ "node": ">=8" } }, + "node_modules/npm": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.2.5.tgz", + "integrity": "sha512-lXdZ7titEN8CH5YJk9C/aYRU9JeDxQ4d8rwIIDsvH3SMjLjHTukB2CFstMiB30zXs4vCrPN2WH6cDq1yHBeJAw==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "strip-ansi", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.2.1", + "@npmcli/config": "^8.0.2", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.2", + "@sigstore/tuf": "^2.2.0", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.1", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.1", + "ini": "^4.1.1", + "init-package-json": "^6.0.0", + "is-cidr": "^5.0.3", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^8.0.1", + "libnpmdiff": "^6.0.3", + "libnpmexec": "^7.0.4", + "libnpmfund": "^5.0.1", + "libnpmhook": "^10.0.0", + "libnpmorg": "^6.0.1", + "libnpmpack": "^6.0.3", + "libnpmpublish": "^9.0.2", + "libnpmsearch": "^7.0.0", + "libnpmteam": "^6.0.0", + "libnpmversion": "^5.0.1", + "make-fetch-happen": "^13.0.0", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.0.1", + "nopt": "^7.2.0", + "normalize-package-data": "^6.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-profile": "^9.0.0", + "npm-registry-fetch": "^16.1.0", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^17.0.5", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.5.4", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "strip-ansi": "^7.1.0", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -5569,251 +5771,2824 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "optional": true, "engines": { - "node": "*" + "node": ">=0.1.90" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" }, - "node_modules/onetime": { + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "peer": true - }, - "node_modules/p-cancelable": { + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.2.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.0.1", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.2", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, "engines": { - "node": ">=6" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "p-limit": "^2.2.0" + "ansi-styles": "^4.3.0" }, "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.3", + "inBundle": true, + "license": "ISC", "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, "bin": { - "semver": "bin/semver.js" + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "inBundle": true, + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, "engines": { - "node": ">=8.6" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, "engines": { - "node": ">= 6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", "dependencies": { - "find-up": "^4.0.0" + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=4" + "node": ">=14" } }, - "node_modules/pretty-format": { - "version": "28.1.3", + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abort-controller": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^4.1.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.0.3", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-columns/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-columns/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/columnify/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/columnify/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/event-target-shim": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/events": { + "version": "3.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.0.3", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "4.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4", + "tar": "^6.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "7.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.1", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "6.0.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.0", + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^2.1.0", + "ssri": "^10.0.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "5.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.3", + "@npmcli/run-script": "^7.0.2", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "17.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.13", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/process": { + "version": "0.11.10", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.5.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.1.0", + "@sigstore/tuf": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.16", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", "dev": true, @@ -6434,7 +9209,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -6598,6 +9372,27 @@ "node": ">=8.0" } }, + "node_modules/topo": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.1.1.tgz", + "integrity": "sha512-ZPrPP5nwzZy1fw9abHQH2k+YarTgp9UMAztcB3MmlcZSif63Eg+az05p6wTDaZmnqpS3Mk7K+2W60iHarlz8Ug==", + "deprecated": "This module has moved and is now available at @hapi/topo. Please update your dependencies as this version is no longer maintained and may contain bugs and security issues.", + "dependencies": { + "hoek": "4.x.x" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/topo/node_modules/hoek": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.3.1.tgz", + "integrity": "sha512-v7E+yIjcHECn973i0xHm4kJkEpv3C8sbYS4344WXbzYqRyiDD7rjnnKo4hsJkejQBAFdRMUGNHySeSPKSH9Rqw==", + "deprecated": "This module has moved and is now available at @hapi/hoek. Please update your dependencies as this version is no longer maintained an may contain bugs and security issues.", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -6653,7 +9448,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -6899,7 +9693,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6969,7 +9762,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", diff --git a/package.json b/package.json index 412bd56..2ccdb3f 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,14 @@ "bcrypt": "^5.1.1", "blipp": "^4.0.2", "dotenv": "^8.6.0", + "hapi-cors": "^1.0.3", "hapi-swagger": "^17.2.0", + "install": "^0.13.0", "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", "mysql2": "^3.6.0", "nodemailer": "^6.9.7", + "npm": "^10.2.5", "request": "^2.88.2", "require-directory": "^2.1.1", "sequelize": "^5.21.11", @@ -48,8 +51,10 @@ "npm": ">=6.12" }, "jest": { - "collectCoverage":true, - "collectCoverageFrom":["lib/application/**/*.js"], - "coverageDirectory":"coverage" + "collectCoverage": true, + "collectCoverageFrom": [ + "lib/application/**/*.js" + ], + "coverageDirectory": "coverage" } } diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 27fd736..1fcc9bc 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -4,7 +4,6 @@ const User = require("../../lib/domain/model/User") const bcrypt = require("bcrypt"); const strategy = require("../../lib/infrastructure/config/strategy"); const Jwt = require("@hapi/jwt"); -const {id_utilisateur} = require("../../lib/domain/model/User"); require('dotenv').config() let server const mockUserRepository = {} @@ -360,4 +359,76 @@ describe('user route', () => { expect(res.statusCode).toBe(403); }) }) + describe("/users/sendResetEmail", ()=>{ + it("should return valid code 200",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed: true}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const res = await server.inject({ + method: 'POST', + url: '/users/sendResetEmail', + payload: { + email:"chanon.mael@gmail.com", + }} + ) + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(1) + }) + it("should return valid code 200 even though the user is not confirmed",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed:false}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const res = await server.inject({ + method: 'POST', + url: '/users/sendResetEmail', + payload: { + email:"chanon.mael@gmail.com", + }} + ) + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(0) + }) + it("should return valid code 200 even though the user doesn't exists",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return null}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const res = await server.inject({ + method: 'POST', + url: '/users/sendResetEmail', + payload: { + email:"chanon.mael@gmail.com", + }} + ) + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(0) + }) + }) + describe("/users/resetPassword", ()=>{ + it("should return valid code 200",async ()=>{ + mockUserRepository.getByResetToken = jest.fn(()=>{return {id:1}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const res = await server.inject({ + method: 'POST', + url: '/users/resetPassword', + payload: { + resetToken: "eztgergrehre", + password:"TestPassword", + }} + ) + expect(res.statusCode).toBe(200); + }) + it("should return valid code 400 error on token",async ()=>{ + mockUserRepository.getByResetToken = jest.fn(()=> null) + mockUserRepository.updateUser = jest.fn(()=>{}) + const res = await server.inject({ + method: 'POST', + url: '/users/resetPassword', + payload: { + resetToken: "eztgergrehre", + password:"TestPassword", + }} + ) + expect(res.statusCode).toBe(400); + }) + }) }); diff --git a/test/unit/application/usecase/user/resetPassword.test.js b/test/unit/application/usecase/user/resetPassword.test.js new file mode 100644 index 0000000..552217c --- /dev/null +++ b/test/unit/application/usecase/user/resetPassword.test.js @@ -0,0 +1,26 @@ +const mockUserRepository = {} +const resetPassword = require("../../../../../lib/application/use_cases/user/resetPassword") +const serviceLocator = { + userRepository: mockUserRepository +} +require("dotenv").config() +const catchError = require("../utils/catchError") +const bcrypt = require("bcrypt"); +describe("sendResetEmail", ()=>{ + it("should return true",async ()=>{ + mockUserRepository.getByResetToken = jest.fn(()=>{return {id:"test"}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const result = await resetPassword("testPassword","token",serviceLocator) + expect(result.password).not.toBe("test"); + expect(mockUserRepository.updateUser).toHaveBeenCalledTimes(1) + }) + it("should return error",async ()=>{ + mockUserRepository.getByResetToken = jest.fn(()=>{}) + mockUserRepository.updateUser = jest.fn(()=>{}) + const error = await catchError(async () =>{ + await resetPassword("testPassword","token",serviceLocator) + }) + expect(error.code).toBe(400) + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/sendResetEmail.test.js b/test/unit/application/usecase/user/sendResetEmail.test.js new file mode 100644 index 0000000..fc36b26 --- /dev/null +++ b/test/unit/application/usecase/user/sendResetEmail.test.js @@ -0,0 +1,29 @@ +const mockMailRepository = {} +const mockUserRepository = {} +const sendResetEmail = require("../../../../../lib/application/use_cases/user/sendResetEmail") +const serviceLocator = { + mailRepository: mockMailRepository, + userRepository: mockUserRepository +} +describe("sendResetEmail", ()=>{ + it("should return true",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed: true}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + + expect(await sendResetEmail("testemail",serviceLocator)).toBe(true); + expect(mockMailRepository.send).toHaveBeenCalledTimes(1) + }) + it("should return false because the user is not confirmed",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed:false}}) + mockUserRepository.updateUser = jest.fn(()=>{}) + expect(await sendResetEmail("testemail",serviceLocator)).toBe(false); + }) + it("should return false because the user doesn't exists",async ()=>{ + mockMailRepository.send = jest.fn(()=>{}) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return null}) + mockUserRepository.updateUser = jest.fn(()=>{}) + expect(await sendResetEmail("testemail",serviceLocator)).toBe(false); + }) +}) \ No newline at end of file From 0262cd87a4a3b13c8b3ae803b18dacab0b8a602b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:35:19 +0100 Subject: [PATCH 17/67] =?UTF-8?q?Feature/fetch=20chanson=C3=83=20correctio?= =?UTF-8?q?n=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ajout de la requete de recuperation des infos d'une chanson ou un album * ajout des test de getAlbum et getTrack * test correction --------- Co-authored-by: yousrah24 --- .../use_cases/spotify/FetchArtist.js | 8 +- lib/application/use_cases/spotify/getAlbum.js | 9 + lib/application/use_cases/spotify/getTrack.js | 8 + lib/domain/entity/SpotifyEntity.js | 21 +- lib/domain/model/Album.js | 1 + .../repositories/SpotifyRepository.js | 205 ++++++------ .../controllers/SpotifyController.js | 28 ++ lib/interfaces/routes/spotify.js | 61 +++- lib/interfaces/serializers/AlbumSerializer.js | 17 +- .../serializers/AlbumTrackSerializer.js | 18 ++ lib/interfaces/serializers/TrackSerializer.js | 20 +- test/integration/spotify.test.js | 75 ++++- .../usecase/spotify/getAlbum.test.js | 63 ++++ .../usecase/spotify/getTrack.test.js | 80 +++++ .../serializers/AlbumSerializer.test.js | 1 + .../serializers/AlbumTrackSerializer.test.js | 25 ++ .../serializers/fixtures/albumFixture.js | 21 +- .../serializers/fixtures/albumTrackFixture.js | 299 ++++++++++++++++++ 18 files changed, 833 insertions(+), 127 deletions(-) create mode 100644 lib/application/use_cases/spotify/getAlbum.js create mode 100644 lib/application/use_cases/spotify/getTrack.js create mode 100644 lib/interfaces/serializers/AlbumTrackSerializer.js create mode 100644 test/unit/application/usecase/spotify/getAlbum.test.js create mode 100644 test/unit/application/usecase/spotify/getTrack.test.js create mode 100644 test/unit/interfaces/serializers/AlbumTrackSerializer.test.js create mode 100644 test/unit/interfaces/serializers/fixtures/albumTrackFixture.js diff --git a/lib/application/use_cases/spotify/FetchArtist.js b/lib/application/use_cases/spotify/FetchArtist.js index 6ff1279..33c786f 100644 --- a/lib/application/use_cases/spotify/FetchArtist.js +++ b/lib/application/use_cases/spotify/FetchArtist.js @@ -2,14 +2,14 @@ const SerializeArtist = require("../../../interfaces/serializers/ArtistSerialize const throwStatusCode = require("../utils/throwStatusCode") module.exports = async (id, {spotifyRepository}) =>{ -let artistInfo = {} + let artistInfo = {} try{ artistInfo = await spotifyRepository.getSpotifyArtist(id) } - catch(error){ + catch(error){ throwStatusCode(400, `l'id : ${id} n'existe pas`) - } - + } + const artist = SerializeArtist(artistInfo) // Formatage de l'objet return artist } \ No newline at end of file diff --git a/lib/application/use_cases/spotify/getAlbum.js b/lib/application/use_cases/spotify/getAlbum.js new file mode 100644 index 0000000..224778b --- /dev/null +++ b/lib/application/use_cases/spotify/getAlbum.js @@ -0,0 +1,9 @@ +const SerializeAlbum = require("../../../interfaces/serializers/AlbumSerializer") +const throwStatusCode = require("../utils/throwStatusCode"); + +module.exports = async (id, {spotifyRepository}) =>{ + const albumInfo = await spotifyRepository.getSpotifyAlbums(id) + if(albumInfo.error) + throwStatusCode(albumInfo.error.status,albumInfo.error.message) + return SerializeAlbum(albumInfo) +} \ No newline at end of file diff --git a/lib/application/use_cases/spotify/getTrack.js b/lib/application/use_cases/spotify/getTrack.js new file mode 100644 index 0000000..e8d73bc --- /dev/null +++ b/lib/application/use_cases/spotify/getTrack.js @@ -0,0 +1,8 @@ +const SerializeTrack = require("../../../interfaces/serializers/TrackSerializer") +const throwStatusCode = require("../utils/throwStatusCode") +module.exports = async (id, {spotifyRepository}) =>{ + const trackInfo = await spotifyRepository.getSpotifyTracks(id) + if(trackInfo.error) + throwStatusCode(trackInfo.error.status,trackInfo.error.message) + return SerializeTrack(trackInfo) +} \ No newline at end of file diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index 198ca17..1cff110 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -1,6 +1,6 @@ // Verifie le bon format de la query / payload const Joi = require('@hapi/joi') -const trackBody = Joi.object().keys({ +const search = Joi.object().keys({ query: Joi .string() .min(1) @@ -9,7 +9,7 @@ const trackBody = Joi.object().keys({ spotify_filter: Joi .string() .max(50) - .optional() + .required() .custom((value, helpers) =>{ const allowedValues = ["track","artist","album"] const tabValue = value.split(",") @@ -25,7 +25,22 @@ const trackBody = Joi.object().keys({ limit: Joi.number().integer().required() }) +const album = Joi.object().keys({ + id: Joi + .string() + .min(1) + .required(), +}) + +const track = Joi.object().keys({ + id: Joi + .string() + .min(1) + .required(), +}) + + const fetchArtist = Joi.object().keys({ query: Joi.string().min(1).required(), //correspond au ID Artist }) -module.exports = {trackBody,fetchArtist} \ No newline at end of file +module.exports = {album,fetchArtist,search,track} \ No newline at end of file diff --git a/lib/domain/model/Album.js b/lib/domain/model/Album.js index 3780df8..3d69208 100644 --- a/lib/domain/model/Album.js +++ b/lib/domain/model/Album.js @@ -8,6 +8,7 @@ module.exports = class { this.images = album.images this.spotify_url = album.spotify_url this.artists = album.artists + this.tracks = album.tracks this.genres = album.genres this.type = "album" } diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index 37c1a80..c33ad25 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -4,103 +4,128 @@ const axios = require("axios"); const spotifyRepositoryAbstract = require('./interfaces/SpotifyRepositoryAbstract') module.exports = class extends spotifyRepositoryAbstract{ - constructor(client_id,client_secret,redirect_URI) { - super(); - this.client_id = client_id - this.client_secret = client_secret - this.redirect_uri = redirect_URI - } + constructor(client_id,client_secret,redirect_URI) { + super(); + this.client_id = client_id + this.client_secret = client_secret + this.redirect_uri = redirect_URI + } - getSpotifyAccessToken() { + getSpotifyAccessToken() { - return axios.post('https://accounts.spotify.com/api/token', null, { - params: { - grant_type: 'client_credentials', - }, - auth: { - username: this.client_id, - password: this.client_secret, - }, - }).then((response) => { - return response.data.access_token - }) - } - - async getSpotifySearchList(query,filter,limit){ - return axios.get('https://api.spotify.com/v1/search', { - headers: { - 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, - }, - params: { - q: query, - type: filter, - limit : limit - }, - }) - .then((response) => { - return response.data + return axios.post('https://accounts.spotify.com/api/token', null, { + params: { + grant_type: 'client_credentials', + }, + auth: { + username: this.client_id, + password: this.client_secret, + }, + }).then((response) => { + return response.data.access_token }) - } - - async getSpotifyArtist(id){ - return axios.get(`https://api.spotify.com/v1/artists/${id}`, { - headers: { - 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, - }, - }) - .then((response) => { - return response.data - }) - } + } - refreshToken(token) { - const payload = new URLSearchParams() - payload.append('grant_type', 'refresh_token'); - payload.append('refresh_token', token); - return axios.post('https://accounts.spotify.com/api/token',payload,{ - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - auth: { - username: this.client_id, - password: this.client_secret, - }, - }) - .then((response) => { - return response.data + async getSpotifySearchList(query,filter,limit){ + return axios.get('https://api.spotify.com/v1/search', { + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, + params: { + q: query, + type: filter, + limit : limit + }, }) - } - getToken(code) { - const payload = new URLSearchParams() - payload.append('grant_type','authorization_code') - payload.append('redirect_uri',this.redirect_uri) - payload.append('code',code) - return axios.post('https://accounts.spotify.com/api/token',payload, - { - validateStatus: function (status) { - return true; - }, - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'Authorization': 'Basic ' + (new Buffer.from(this.client_id + ':' + this.client_secret).toString('base64')) - }, + .then((response) => { + return response.data + }) + } + + async getSpotifyAlbums(id){ + return axios.get('https://api.spotify.com/v1/albums/'+ id, { + validateStatus: function (status) { + return true; + }, + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + } }) - .then((response) => { - return response.data + .then((response) => { + return response.data + }) + } + async getSpotifyArtist(id){ + return axios.get(`https://api.spotify.com/v1/artists/${id}`, { + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, }) - } - getAccountData(accessToken) { - return axios.get('https://api.spotify.com/v1/me', { - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) - .then(response => { - return response.data - }) - } + .then((response) => { + return response.data + }) + } + async getSpotifyTracks(id){ + return axios.get('https://api.spotify.com/v1/tracks/'+ id, { + validateStatus: function (status) { + return true; + }, + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + } + }) + .then((response) => { + return response.data + }) + } - }; + refreshToken(token) { + const payload = new URLSearchParams() + payload.append('grant_type', 'refresh_token'); + payload.append('refresh_token', token); + return axios.post('https://accounts.spotify.com/api/token',payload,{ + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + auth: { + username: this.client_id, + password: this.client_secret, + }, + }) + .then((response) => { + return response.data + }) + } + getToken(code) { + const payload = new URLSearchParams() + payload.append('grant_type','authorization_code') + payload.append('redirect_uri',this.redirect_uri) + payload.append('code',code) + return axios.post('https://accounts.spotify.com/api/token',payload, + { + validateStatus: function (status) { + return true; + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + (new Buffer.from(this.client_id + ':' + this.client_secret).toString('base64')) + }, + }) + .then((response) => { + return response.data + }) + } + getAccountData(accessToken) { + return axios.get('https://api.spotify.com/v1/me', { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) + .then(response => { + return response.data + }) + } +}; diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index 247a8d4..7a4152e 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -1,6 +1,8 @@ const search = require("../../application/use_cases/spotify/Search"); +const getAlbum = require("../../application/use_cases/spotify/getAlbum"); const getAuthURL = require("../../application/use_cases/spotify/GetAuthURL"); const fetchArtist = require("../../application/use_cases/spotify/FetchArtist"); +const getTrack = require("../../application/use_cases/spotify/getTrack"); const handleError = require("./utils/handleError"); module.exports = { @@ -19,6 +21,32 @@ module.exports = { } }, + async getAlbums(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; + // Input + let {id} = request.query + const result = await getAlbum(id, serviceLocator) + return handler.response(result).code(200) + }catch(error){ + return handleError(error) + } + }, + + async getTracks(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; + // Input + let {id} = request.query + const result = await getTrack(id, serviceLocator) + return handler.response(result).code(200) + }catch(error){ + return handleError(error) + } + }, + async fetchArtist(request,handler){ try { const serviceLocator = request.server.app.serviceLocator; diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index dfe7087..84f0522 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -1,4 +1,4 @@ -const { trackBody,fetchArtist } = require("../../domain/entity/SpotifyEntity") +const {search, album, track,fetchArtist} = require("../../domain/entity/SpotifyEntity") const spotify = require("../controllers/SpotifyController") module.exports = { name: 'spotify', @@ -11,7 +11,7 @@ module.exports = { path: '/spotify/search', handler: spotify.search, options: { - description: 'get a spotify track', + description: 'get a spotify search', tags: ['api'], plugins: { 'hapi-swagger': { @@ -28,7 +28,7 @@ module.exports = { } }, validate: { - query: trackBody + query: search } }, }, @@ -79,6 +79,61 @@ module.exports = { } } }, + validate: { + query: search + } + }, + }, + { + method: 'GET', + path: '/spotify/album', + handler: spotify.getAlbums, + options: { + description: 'get a spotify album', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + query: album + } + }, + }, + { + method: 'GET', + path: '/spotify/track', + handler: spotify.getTracks, + options: { + description: 'get a spotify track', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + query: track + } }, } ]); diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index 508d554..cdccd6f 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -1,6 +1,9 @@ -const Album = require("../../domain/model/Album") -const ArtistSerializer = require("./ArtistSerializer") +const Album = require("../../domain/model/Album"); +const SerializeArtist = require("./ArtistSerializer"); +const SerializeTrack = require("./AlbumTrackSerializer"); + const serializeAlbum = (albumRaw) => { + const tracks = albumRaw.tracks?.items ? albumRaw.tracks?.items.map(item => SerializeTrack(item)) : undefined const album = { id: albumRaw.id, total_tracks: albumRaw.total_tracks, @@ -8,10 +11,12 @@ const serializeAlbum = (albumRaw) => { name: albumRaw.name, images: albumRaw.images, release_date : albumRaw.release_date, - artists : albumRaw?.artists?.map(item => ArtistSerializer(item)), + artists : albumRaw?.artists?.map(item => SerializeArtist(item)), + tracks: tracks, genres : albumRaw?.genres, - popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100) - } + popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), + }; return new Album(album) } -module.exports = serializeAlbum \ No newline at end of file + +module.exports = serializeAlbum diff --git a/lib/interfaces/serializers/AlbumTrackSerializer.js b/lib/interfaces/serializers/AlbumTrackSerializer.js new file mode 100644 index 0000000..8be5481 --- /dev/null +++ b/lib/interfaces/serializers/AlbumTrackSerializer.js @@ -0,0 +1,18 @@ +// TrackSerializer.js +const Track = require("../../domain/model/Track"); +const SerializeArtist = require("./ArtistSerializer"); + +const serializeTrack = (trackRaw) => { + const track = { + id: trackRaw.id, + name: trackRaw.name, + album: undefined, + artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, + spotify_url : trackRaw.external_urls.spotify, + duration_ms: trackRaw.duration_ms, + popularity: undefined, + }; + return new Track(track) +}; + +module.exports = serializeTrack \ No newline at end of file diff --git a/lib/interfaces/serializers/TrackSerializer.js b/lib/interfaces/serializers/TrackSerializer.js index 66600e1..283c9b1 100644 --- a/lib/interfaces/serializers/TrackSerializer.js +++ b/lib/interfaces/serializers/TrackSerializer.js @@ -1,18 +1,20 @@ -const Track = require("../../domain/model/Track") -const SerializeAlbum = require("./AlbumSerializer") -const SerializeArtist = require("./ArtistSerializer") -const serializeTrack = (trackRaw) => { - const album = trackRaw.album ? SerializeAlbum(trackRaw?.album) : undefined +// TrackSerializer.js +const Track = require("../../domain/model/Track"); +const SerializeAlbum = require("./AlbumSerializer"); +const SerializeArtist = require("./ArtistSerializer"); + +const serializeTrack = (trackRaw) => { + const album = trackRaw?.album ? SerializeAlbum(trackRaw.album) : undefined const track = { id: trackRaw.id, name: trackRaw.name, album: album, - artists: trackRaw?.artists?.map(item => SerializeArtist(item)), + artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, spotify_url : trackRaw.external_urls.spotify, duration_ms: trackRaw.duration_ms, - popularity:trackRaw.popularity, - } + popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, + }; return new Track(track) -} +}; module.exports = serializeTrack \ No newline at end of file diff --git a/test/integration/spotify.test.js b/test/integration/spotify.test.js index bdee86d..47f367a 100644 --- a/test/integration/spotify.test.js +++ b/test/integration/spotify.test.js @@ -2,12 +2,21 @@ const Hapi = require('@hapi/hapi'); const User = require("../../lib/domain/model/User") const bcrypt = require("bcrypt"); +const { + rawTrackWithServeralArtists, +} = require("../unit/interfaces/serializers/fixtures/trackFixture"); +const { + albumRawOneArtist, +} = require("../unit/interfaces/serializers/fixtures/albumFixture"); + +const getTrack = require("../../lib/application/use_cases/spotify/getTrack"); +const catchError = require("../unit/application/usecase/utils/catchError"); let server const mockUserRepository = {} const mockSpotifyRepository = {} mockUserRepository.getUsersByPseudo = jest.fn((pseudo) =>{return []}) mockSpotifyRepository.getSpotifySearchList = jest.fn((query, filter, limitSize) =>{return {}}) -mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{return {}}) // mock la fct de repo +mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{return {}}) // mock la fct de repo describe('spotify route', () => { @@ -86,22 +95,68 @@ describe('spotify route', () => { } }); }), - - describe("/spotify/FetchArtist", ()=>{ - it('should respond code 400 invalid query/id', async () => { + + describe("/spotify/FetchArtist", ()=>{ + it('should respond code 400 invalid query/id', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=`, + }); + expect(res1.statusCode).toBe(400); + }); + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=query`, + }); + expect(res1.statusCode).toBe(200); + }); + }) + describe('/spotify/track', () => { + it("should invalid return code 400", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return {error : {status: 400, message: "msg"}} + }) const res1 = await server.inject({ method: 'GET', - url: `/spotify/fetchArtist?query=`, + url: `/spotify/track?id=29092`, }); expect(res1.statusCode).toBe(400); - }); - it('should respond code 200', async () => { + }) + it("should invalid return code 200", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return rawTrackWithServeralArtists + }) const res1 = await server.inject({ method: 'GET', - url: `/spotify/fetchArtist?query=query`, + url: `/spotify/track?id=29092`, }); expect(res1.statusCode).toBe(200); - }); - }) + }) + + }); + describe('/spotify/album', () => { + it("should invalid return code 400", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return {error : {status: 400, message: "msg"}} + }) + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/album?id=29092`, + }); + expect(res1.statusCode).toBe(400); + }) + it("should invalid return code 200", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return albumRawOneArtist + }) + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/album?id=29092`, + }); + expect(res1.statusCode).toBe(200); + }) + + }); }); diff --git a/test/unit/application/usecase/spotify/getAlbum.test.js b/test/unit/application/usecase/spotify/getAlbum.test.js new file mode 100644 index 0000000..9cb9ff8 --- /dev/null +++ b/test/unit/application/usecase/spotify/getAlbum.test.js @@ -0,0 +1,63 @@ +const getAlbum = require("../../../../../lib/application/use_cases/spotify/getalbum") + +const { + albumRawOneArtist, + expectedAlbumOneArtist, + albumRawSeveralArtist, + expectedAlbumSeveralArtist, + albumRawNoArtist, + expectedAlbumNoArtist +} = require("../../../interfaces/serializers/fixtures/albumTrackFixture") +const catchError = require("../utils/catchError"); +const mockSpotifyRepository = {} + + +describe('get an album usecase', () => { + it("should return an album with one artist", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return albumRawOneArtist + }) + const result = await getAlbum( + "45i3tB9z0dgJ33olyrsLUz", + {spotifyRepository : mockSpotifyRepository} + ) + result.popularity = 0 + expect(result).toEqual(expectedAlbumOneArtist) + }) + it("should return an album with one artist", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return albumRawSeveralArtist + }) + const result = await getAlbum( + "45i3tB9z0dgJ33olyrsLUz", + {spotifyRepository : mockSpotifyRepository} + ) + result.popularity = 0 + expect(result).toEqual(expectedAlbumSeveralArtist) + }) + it("should return an album with one artist", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return albumRawNoArtist + }) + const result = await getAlbum( + "45i3tB9z0dgJ33olyrsLUz", + {spotifyRepository : mockSpotifyRepository} + ) + result.popularity = 0 + expect(result).toEqual(expectedAlbumNoArtist) + }) + it("should throw error", async ()=>{ + mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ + return {error : {status: 400, message: "msg"}} + }) + + const error = await catchError(async ()=>{ + await getAlbum( + "45i3tB9z0dgJ33olyrsLUz", + {spotifyRepository : mockSpotifyRepository} + ) + }) + expect(error.code).toBe(400) + }) +}); +//see \ No newline at end of file diff --git a/test/unit/application/usecase/spotify/getTrack.test.js b/test/unit/application/usecase/spotify/getTrack.test.js new file mode 100644 index 0000000..63d35cf --- /dev/null +++ b/test/unit/application/usecase/spotify/getTrack.test.js @@ -0,0 +1,80 @@ +const getTrack = require("../../../../../lib/application/use_cases/spotify/getTrack") +const { + expectedRawTrackWithOneArtistOneAlbum, + rawTrackWithOneArtistOneAlbum, + expectedRawTrackWithServeralArtists, + rawTrackWithServeralArtists, + expectedRawTrackWithNoArtist, + rawTrackWithNoArtist, + rawTrackWithNoAlbum, + expectedRawTrackWithNoAlbum +} = require("../../../interfaces/serializers/fixtures/trackFixture") +const catchError = require("../utils/catchError"); + +const mockSpotifyRepository = {} + + +describe('get a track usecase', () => { + it("should return a track with one artist", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return rawTrackWithOneArtistOneAlbum + }) + + const result = await getTrack( + "3YP99J8wTzG55t1cFmd6iq", + {spotifyRepository : mockSpotifyRepository} + ) + expectedRawTrackWithOneArtistOneAlbum.album.popularity = result.album.popularity + expect(result).toEqual(expectedRawTrackWithOneArtistOneAlbum) + }) + it("should return a track with several artists", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return rawTrackWithServeralArtists + }) + + const result = await getTrack( + "3YP99J8wTzG55t1cFmd6iq", + {spotifyRepository : mockSpotifyRepository} + ) + expectedRawTrackWithOneArtistOneAlbum.album.popularity = result.album.popularity + expect(result).toEqual(expectedRawTrackWithServeralArtists) + }) + it("should return a track with no artist", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return rawTrackWithNoArtist + }) + + const result = await getTrack( + "3YP99J8wTzG55t1cFmd6iq", + {spotifyRepository : mockSpotifyRepository} + ) + expectedRawTrackWithOneArtistOneAlbum.album.popularity = result.album.popularity + expect(result).toEqual(expectedRawTrackWithNoArtist) + }) + it("should return a track with no album", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return rawTrackWithNoAlbum + }) + + const result = await getTrack( + "3YP99J8wTzG55t1cFmd6iq", + {spotifyRepository : mockSpotifyRepository} + ) + expect(result).toEqual(expectedRawTrackWithNoAlbum) + }) + it("should throw error", async ()=>{ + mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ + return {error : {status: 400, message: "msg"}} + }) + + const error = await catchError(async ()=>{ + await getTrack( + "3YP99J8wTzG55t1cFmd6iq", + {spotifyRepository : mockSpotifyRepository} + ) + }) + expect(error.code).toBe(400) + }) +}); + + diff --git a/test/unit/interfaces/serializers/AlbumSerializer.test.js b/test/unit/interfaces/serializers/AlbumSerializer.test.js index 09edb99..f835ac2 100644 --- a/test/unit/interfaces/serializers/AlbumSerializer.test.js +++ b/test/unit/interfaces/serializers/AlbumSerializer.test.js @@ -7,6 +7,7 @@ const { albumRawNoArtist, expectedAlbumNoArtist } = require("./fixtures/albumFixture") + describe('albumSerialize', () => { it("should return serialized album with one artist", ()=>{ const result = serializeAlbum(albumRawOneArtist) diff --git a/test/unit/interfaces/serializers/AlbumTrackSerializer.test.js b/test/unit/interfaces/serializers/AlbumTrackSerializer.test.js new file mode 100644 index 0000000..3bedde1 --- /dev/null +++ b/test/unit/interfaces/serializers/AlbumTrackSerializer.test.js @@ -0,0 +1,25 @@ +const serializeTrack = require("../../../../lib/interfaces/serializers/AlbumTrackSerializer") +const { + expectedRawTrackWithOneArtist, + rawTrackWithOneArtist, + expectedRawTrackWithServeralArtists, + rawTrackWithServeralArtists, + expectedRawTrackWithNoArtist, + rawTrackWithNoArtist, +} = require("./fixtures/albumTrackFixture") + +describe('AlbumTrackSerializer', () => { + it("should return serialized track with one artist", ()=>{ + const result = serializeTrack(rawTrackWithOneArtist) + expect(result).toEqual(expectedRawTrackWithOneArtist) + }) + + it("should return serialized track with several artists", ()=>{ + const result = serializeTrack(rawTrackWithServeralArtists) + expect(result).toEqual(expectedRawTrackWithServeralArtists) + }) + it("should return serialized track with no artist", ()=>{ + const result = serializeTrack(rawTrackWithNoArtist) + expect(result).toEqual(expectedRawTrackWithNoArtist) + }) +}); \ No newline at end of file diff --git a/test/unit/interfaces/serializers/fixtures/albumFixture.js b/test/unit/interfaces/serializers/fixtures/albumFixture.js index 5a35a54..0c4d636 100644 --- a/test/unit/interfaces/serializers/fixtures/albumFixture.js +++ b/test/unit/interfaces/serializers/fixtures/albumFixture.js @@ -2,6 +2,16 @@ const { artistFixture, expectedFixture, } = require("./artistFixture") + +const { + expectedRawTrackWithOneArtist, + rawTrackWithOneArtist, + expectedRawTrackWithServeralArtists, + rawTrackWithServeralArtists, + expectedRawTrackWithNoArtist, + rawTrackWithNoArtist, +} = require('./albumTrackFixture') + const albumRawOneArtist = { album_type: "album", total_tracks: 14, @@ -31,7 +41,8 @@ const albumRawOneArtist = { genres: ["genre1", "genre2"], artists: [ artistFixture - ] + ], + tracks: undefined, } const expectedAlbumOneArtist = { @@ -61,6 +72,7 @@ const expectedAlbumOneArtist = { artists:[ expectedFixture ], + tracks: undefined, genres: ["genre1", "genre2"], type:"album" } @@ -95,7 +107,9 @@ const albumRawSeveralArtist = { artists: [ artistFixture, artistFixture - ] + ], + tracks: undefined, + } const expectedAlbumSeveralArtist = { @@ -126,6 +140,7 @@ const expectedAlbumSeveralArtist = { expectedFixture, expectedFixture ], + tracks: undefined, genres: ["genre1", "genre2"], type:"album" } @@ -154,6 +169,7 @@ const albumRawNoArtist = { width: 64 } ], + tracks: undefined, name: "Perdu D'Avance", release_date: "2009-02-16", genres: ["genre1", "genre2"], @@ -184,6 +200,7 @@ const expectedAlbumNoArtist = { } ], artists: undefined, + tracks: undefined, genres: ["genre1", "genre2"], type:"album" } diff --git a/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js b/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js new file mode 100644 index 0000000..792bd82 --- /dev/null +++ b/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js @@ -0,0 +1,299 @@ +const { + artistFixture, + expectedFixture, +} = require("./artistFixture") + + +const rawTrackWithOneArtist = { + id: "test_id", + name: "test_name", + external_urls: { + spotify: "https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA" + }, + artists: [ + artistFixture + ], + duration_ms: 12121231, +} + +const expectedRawTrackWithOneArtist = { + id:"test_id", + name:"test_name", + album: undefined, + artists:[ + expectedFixture + ], + duration_ms:12121231, + popularity: undefined, + spotify_url:"https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA", + type:"track" +} + +const rawTrackWithServeralArtists = { + id: "test_id", + name: "test_name", + external_urls: { + spotify: "https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA" + }, + artists: [ + artistFixture + ], + duration_ms: 12121231, +} + +const expectedRawTrackWithServeralArtists = { + id:"test_id", + name:"test_name", + album: undefined, + artists:[ + expectedFixture + ], + duration_ms:12121231, + popularity: undefined, + spotify_url:"https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA", + type:"track" +} + + +const rawTrackWithNoArtist = { + id: "test_id", + name: "test_name", + external_urls: { + spotify: "https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA" + }, + artists: undefined, + duration_ms: 12121231, +} + +const expectedRawTrackWithNoArtist = { + id:"test_id", + name:"test_name", + artists: undefined, + album: undefined, + duration_ms:12121231, + popularity: undefined, + spotify_url:"https://open.spotify.com/track/4UVDKEPTgMQXv9UlIqVTcA", + type:"track" +} + +const albumRawOneArtist = { + album_type: "album", + total_tracks: 14, + external_urls: { + spotify: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy" + }, + id: "17UiqpQyl8T8vVxz2Towjy", + images: [ + { + url: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height: 640, + width: 640 + }, + { + url: "https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height: 300, + width: 300 + }, + { + url: "https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height: 64, + width: 64 + } + ], + name: "Perdu D'Avance", + release_date: "2009-02-16", + genres: ["genre1", "genre2"], + artists: [ + artistFixture + ], + tracks: {items : [ + rawTrackWithOneArtist + ]}, +} + +const expectedAlbumOneArtist = { + id:"17UiqpQyl8T8vVxz2Towjy", + total_tracks:14, + name:"Perdu D'Avance", + popularity:0, + release_date:"2009-02-16", + spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", + images:[ + { + url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height:640, + width:640 + }, + { + url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height:300, + width:300 + }, + { + url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height:64, + width:64 + } + ], + artists:[ + expectedFixture + ], + tracks: [ + expectedRawTrackWithOneArtist + + ], + genres: ["genre1", "genre2"], + type:"album" +} + +const albumRawSeveralArtist = { + album_type: "album", + total_tracks: 14, + external_urls: { + spotify: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy" + }, + id: "17UiqpQyl8T8vVxz2Towjy", + images: [ + { + url: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height: 640, + width: 640 + }, + { + url: "https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height: 300, + width: 300 + }, + { + url: "https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height: 64, + width: 64 + } + ], + name: "Perdu D'Avance", + release_date: "2009-02-16", + genres: ["genre1", "genre2"], + artists: [ + artistFixture, + artistFixture + ], + tracks: { + items: [ + rawTrackWithServeralArtists + ], + } + +} + +const expectedAlbumSeveralArtist = { + id:"17UiqpQyl8T8vVxz2Towjy", + total_tracks:14, + name:"Perdu D'Avance", + popularity:0, + release_date:"2009-02-16", + spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", + images:[ + { + url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height:640, + width:640 + }, + { + url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height:300, + width:300 + }, + { + url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height:64, + width:64 + } + ], + artists:[ + expectedFixture, + expectedFixture + ], + tracks: [ + expectedRawTrackWithServeralArtists + ], + genres: ["genre1", "genre2"], + type:"album" +} + +const albumRawNoArtist = { + album_type: "album", + total_tracks: 14, + external_urls: { + spotify: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy" + }, + id: "17UiqpQyl8T8vVxz2Towjy", + images: [ + { + url: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height: 640, + width: 640 + }, + { + url: "https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height: 300, + width: 300 + }, + { + url: "https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height: 64, + width: 64 + } + ], + tracks: {items: [ + rawTrackWithNoArtist + ]}, + name: "Perdu D'Avance", + release_date: "2009-02-16", + genres: ["genre1", "genre2"], +} + +const expectedAlbumNoArtist = { + id:"17UiqpQyl8T8vVxz2Towjy", + total_tracks:14, + name:"Perdu D'Avance", + popularity:0, + release_date:"2009-02-16", + spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", + images:[ + { + url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + height:640, + width:640 + }, + { + url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", + height:300, + width:300 + }, + { + url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", + height:64, + width:64 + } + ], + artists: undefined, + tracks: [ + expectedRawTrackWithNoArtist + ], + genres: ["genre1", "genre2"], + type:"album" +} +module.exports = { + expectedRawTrackWithOneArtist, + rawTrackWithOneArtist, + expectedRawTrackWithServeralArtists, + rawTrackWithServeralArtists, + expectedRawTrackWithNoArtist, + rawTrackWithNoArtist, + albumRawOneArtist, + expectedAlbumOneArtist, + albumRawSeveralArtist, + expectedAlbumSeveralArtist, + albumRawNoArtist, + expectedAlbumNoArtist +} \ No newline at end of file From 19f3b3047da26b66e4ccfea7cdd2324a041300ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 16 Jan 2024 21:32:00 +0100 Subject: [PATCH 18/67] search refactor (#23) * refactor * refactor 2 --- .../use_cases/spotify/RefreshToken.js | 13 ++-- lib/application/use_cases/spotify/Search.js | 13 ++-- .../use_cases/spotify/getSearchFilters.js | 20 +++++++ lib/domain/entity/SpotifyEntity.js | 3 +- lib/domain/entity/UserEntity.js | 32 +++++++++- .../repositories/SpotifyRepository.js | 1 - lib/infrastructure/routine/refreshTokens.js | 2 +- .../controllers/SpotifyController.js | 13 +++- lib/interfaces/routes/spotify.js | 26 +++++++- lib/interfaces/routes/users.js | 37 +++++------- test/integration/spotify.test.js | 59 +++++++++---------- .../usecase/spotify/Search.test.js | 6 +- .../usecase/spotify/getSearchFilters.test.js | 27 +++++++++ 13 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 lib/application/use_cases/spotify/getSearchFilters.js create mode 100644 test/unit/application/usecase/spotify/getSearchFilters.test.js diff --git a/lib/application/use_cases/spotify/RefreshToken.js b/lib/application/use_cases/spotify/RefreshToken.js index 91366ad..f7d2558 100644 --- a/lib/application/use_cases/spotify/RefreshToken.js +++ b/lib/application/use_cases/spotify/RefreshToken.js @@ -1,14 +1,19 @@ -module.exports = (id,instant_refresh,{spotifyRepository,userRepository}) => { +module.exports = async (user,instant_refresh,{spotifyRepository,userRepository}) => { const action = async ()=>{ - const user = await userRepository.getByUser(id) const {access_token} = await spotifyRepository.refreshToken(user.refresh_token) user.token = access_token userRepository.updateUser(user) } if(instant_refresh){ - action() + try{ + await action() + }catch(e){ + user.refresh_token = null + user.token = null + await userRepository.updateUser(user) + } } - setInterval(()=>{ + setInterval(async ()=>{ action() },3500*1000) } \ No newline at end of file diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index e006737..a9d7813 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -2,15 +2,20 @@ const SerializeTrack = require("../../../interfaces/serializers/TrackSerializer" const SerializeAlbum = require("../../../interfaces/serializers/AlbumSerializer") const SerializeArtist = require("../../../interfaces/serializers/ArtistSerializer") const MAX_USER = 3 -module.exports = async (query,filter, limit,allow_user, {spotifyRepository, userRepository}) =>{ +module.exports = async (query,filter, limit, {spotifyRepository, userRepository}) =>{ let limitSize = limit let users = [] - if(allow_user){ - + console.log(filter) + filter = filter.split(",") + if(filter.includes("user")){ + filter = filter.filter(item => item !== "user"); users = await userRepository.getUsersByPseudo(query,MAX_USER) limitSize -= users.length } - const searchListRaw = await spotifyRepository.getSpotifySearchList(query, filter, limitSize) + + const searchListRaw = filter.length>0 + ? await spotifyRepository.getSpotifySearchList(query, filter.join(","), limitSize) + : {} let returnValue = [] // Formatage des objets diff --git a/lib/application/use_cases/spotify/getSearchFilters.js b/lib/application/use_cases/spotify/getSearchFilters.js new file mode 100644 index 0000000..eca493b --- /dev/null +++ b/lib/application/use_cases/spotify/getSearchFilters.js @@ -0,0 +1,20 @@ +module.exports = () => { + return [ + { + label: 'Musique', + id: 'track' + }, + { + label: 'Artiste', + id: 'artist' + }, + { + label: 'Album', + id: 'album' + }, + { + label: 'Utilisateur', + id: 'user' + }, + ] +} \ No newline at end of file diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index 1cff110..6c968a0 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -11,7 +11,7 @@ const search = Joi.object().keys({ .max(50) .required() .custom((value, helpers) =>{ - const allowedValues = ["track","artist","album"] + const allowedValues = ["track","artist","album","user"] const tabValue = value.split(",") let correct = true tabValue.forEach((value) => { @@ -21,7 +21,6 @@ const search = Joi.object().keys({ }) return correct ? value : helpers.error('any.invalid') }), - allow_user: Joi.boolean().required(), limit: Joi.number().integer().required() }) diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index f6a1091..ed80e73 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -18,5 +18,33 @@ const userSignUp = Joi.object().keys({ confirmToken: Joi.string().max(50), bio:Joi.string().min(0).max(1500).error(validationErrror("bio","la bio doit faire moins de 1500 caractères")), }) - -module.exports = {userSignIn, userSignUp} \ No newline at end of file +const uploadPreview = Joi.object().keys({ + file: Joi.any() +}) +const createUser = Joi.object().keys({ + email: Joi.string().email().min(10).max(40), + spotify_code: Joi.string().max(1000), +}) +const isUser = Joi.object().keys({ + pseudo: Joi.string().min(3).max(15).required(), +}) +const getUserByConfirmToken = Joi.object().keys({ + confirmToken: Joi.string().max(50).required(), +}) +const sendResetEmail = Joi.object().keys({ + email: Joi.string().email().min(10).max(40) +}) +const resetPassword = Joi.object().keys({ + resetToken: Joi.string().max(50).required(), + password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")) +}) +module.exports = { + userSignIn, + userSignUp, + uploadPreview, + createUser, + isUser, + getUserByConfirmToken, + sendResetEmail, + resetPassword +} \ No newline at end of file diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index c33ad25..e83612b 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -13,7 +13,6 @@ module.exports = class extends spotifyRepositoryAbstract{ getSpotifyAccessToken() { - return axios.post('https://accounts.spotify.com/api/token', null, { params: { grant_type: 'client_credentials', diff --git a/lib/infrastructure/routine/refreshTokens.js b/lib/infrastructure/routine/refreshTokens.js index 8690fd0..f7a7341 100644 --- a/lib/infrastructure/routine/refreshTokens.js +++ b/lib/infrastructure/routine/refreshTokens.js @@ -2,6 +2,6 @@ const refreshToken = require("../../application/use_cases/spotify/RefreshToken") module.exports = async ({spotifyRepository, userRepository}) => { const users = await userRepository.getSpotifyAuthUser() for(let i = 0; i { it('should respond code 400 invalid query', async () => { const res1 = await server.inject({ method: 'GET', - url: `/spotify/search?query=${"a".repeat(51)}&spotify_filter=trddzack&allow_user=true&limit=10`, + url: `/spotify/search?query=${"a".repeat(51)}&spotify_filter=trddzack&limit=10`, }); expect(res1.statusCode).toBe(400); }); it('should respond code 400 invalid spotify_filter', async () => { const res1 = await server.inject({ method: 'GET', - url: `/spotify/search?query=query&spotify_filter=trddzack&allow_user=true&limit=10`, - }); - - expect(res1.statusCode).toBe(400); - }); - it('should respond code 400 invalid allow_user', async () => { - const res1 = await server.inject({ - method: 'GET', - url: `/spotify/search?query=query&spotify_filter=track&allow_user=dsd&limit=10`, + url: `/spotify/search?query=query&spotify_filter=trddzack&limit=10`, }); expect(res1.statusCode).toBe(400); @@ -62,13 +54,13 @@ describe('spotify route', () => { it('should respond code 400 invalid limit', async () => { const res1 = await server.inject({ method: 'GET', - url: `/spotify/search?query=query&spotify_filter=track&allow_user=true&limit=cez`, + url: `/spotify/search?query=query&spotify_filter=track&limit=cez`, }); expect(res1.statusCode).toBe(400); }); it('should respond code 200', async () => { - const allowedValues = ["track","artist","album"] + const allowedValues = ["track","artist","album","user"] const getAllSubset = (array) =>{ const n = array.length; const allSubsets = []; @@ -89,30 +81,37 @@ describe('spotify route', () => { for(let i =1; i{ - it('should respond code 400 invalid query/id', async () => { - const res1 = await server.inject({ - method: 'GET', - url: `/spotify/fetchArtist?query=`, - }); - expect(res1.statusCode).toBe(400); + }) + describe("/spotify/Searchfilters", ()=>{ + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/Searchfilters`, }); - it('should respond code 200', async () => { - const res1 = await server.inject({ - method: 'GET', - url: `/spotify/fetchArtist?query=query`, - }); - expect(res1.statusCode).toBe(200); + expect(res1.statusCode).toBe(200); + }); + }) + describe("/spotify/FetchArtist", ()=>{ + it('should respond code 400 invalid query/id', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=`, }); - }) + expect(res1.statusCode).toBe(400); + }); + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=query`, + }); + expect(res1.statusCode).toBe(200); + }); + }) describe('/spotify/track', () => { it("should invalid return code 400", async ()=>{ mockSpotifyRepository.getSpotifyTracks = jest.fn((id) =>{ diff --git a/test/unit/application/usecase/spotify/Search.test.js b/test/unit/application/usecase/spotify/Search.test.js index 3e6a78c..06b7810 100644 --- a/test/unit/application/usecase/spotify/Search.test.js +++ b/test/unit/application/usecase/spotify/Search.test.js @@ -21,7 +21,6 @@ describe('Search usecase', () => { "test", "filter", 4, - false, {spotifyRepository : mockSpotifyRepository,userRepository: mockUserRepository}) expect(result).toEqual(expectedSearchResult) expect(mockSpotifyRepository.getSpotifySearchList).toHaveBeenCalledWith("test","filter",4) @@ -29,12 +28,11 @@ describe('Search usecase', () => { it("should return item list sorted by popularity with user", async ()=>{ const result = await Search( "test", - "filter", + "user,track", 4, - true, {spotifyRepository : mockSpotifyRepository,userRepository: mockUserRepository}) expect(result).toEqual(expectedSearchResultWithUsers) - expect(mockSpotifyRepository.getSpotifySearchList).toHaveBeenCalledWith("test","filter",3) + expect(mockSpotifyRepository.getSpotifySearchList).toHaveBeenCalledWith("test","track",3) }) }); //see \ No newline at end of file diff --git a/test/unit/application/usecase/spotify/getSearchFilters.test.js b/test/unit/application/usecase/spotify/getSearchFilters.test.js new file mode 100644 index 0000000..f537ffa --- /dev/null +++ b/test/unit/application/usecase/spotify/getSearchFilters.test.js @@ -0,0 +1,27 @@ +const getSearchFilters = require("../../../../../lib/application/use_cases/spotify/getSearchFilters") + + +describe('getSearchFilters usecase', () => { + it("should return list of filters", async ()=>{ + const expectSearchFixture = [ + { + label: 'Musique', + id: 'track' + }, + { + label: 'Artiste', + id: 'artist' + }, + { + label: 'Album', + id: 'album' + }, + { + label: 'Utilisateur', + id: 'user' + }, + ] + const result = getSearchFilters() + expect(result).toEqual(expectSearchFixture) + }) +}); \ No newline at end of file From ba871505f36789ce2ed288458ba44afe72b7d376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:15:13 +0100 Subject: [PATCH 19/67] added follow endpoint (#24) --- .gitignore | 4 +- lib/application/use_cases/user/follow.js | 14 ++++ lib/domain/entity/UserEntity.js | 10 ++- lib/infrastructure/config/service-locator.js | 4 +- .../repositories/FollowRepository.js | 64 ++++++++++++++++++ .../repositories/SpotifyRepository.js | 3 + .../interfaces/FollowRepositoryAbstract.js | 17 +++++ .../controllers/SpotifyController.js | 3 +- lib/interfaces/controllers/UsersController.js | 16 +++++ lib/interfaces/routes/spotify.js | 2 +- lib/interfaces/routes/users.js | 32 ++++++++- test/integration/users.test.js | 66 ++++++++++++++++++- .../usecase/spotify/follow.test.js | 59 +++++++++++++++++ 13 files changed, 284 insertions(+), 10 deletions(-) create mode 100644 lib/application/use_cases/user/follow.js create mode 100644 lib/infrastructure/repositories/FollowRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js create mode 100644 test/unit/application/usecase/spotify/follow.test.js diff --git a/.gitignore b/.gitignore index 9f42944..c2fb61f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ node_modules upload docker-compose.yml -.env \ No newline at end of file +.env + +coverage/ \ No newline at end of file diff --git a/lib/application/use_cases/user/follow.js b/lib/application/use_cases/user/follow.js new file mode 100644 index 0000000..ce518c6 --- /dev/null +++ b/lib/application/use_cases/user/follow.js @@ -0,0 +1,14 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (userToken, artistId, {userRepository,followRepository,accessTokenManager,spotifyRepository}) =>{ + const id = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + const artist = spotifyRepository.getSpotifyArtist(artistId) + if(artist.error) throwStatusCode(artist.error.status,artist.error.message) + if(! await followRepository.doesFollows(id,artistId)) { + followRepository.follow(id,artistId) + return true + } + followRepository.unfollow(id,artistId) + return false + +} \ No newline at end of file diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index ed80e73..8783418 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -38,6 +38,13 @@ const resetPassword = Joi.object().keys({ resetToken: Joi.string().max(50).required(), password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")) }) +const follow = Joi.object().keys({ + artistId: Joi + .string() + .min(1) + .required(), +}) + module.exports = { userSignIn, userSignUp, @@ -46,5 +53,6 @@ module.exports = { isUser, getUserByConfirmToken, sendResetEmail, - resetPassword + resetPassword, + follow } \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index f60e296..adc930c 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -7,6 +7,7 @@ const spotifyRepository= require('../repositories/SpotifyRepository'); const JwtAccessTokenManager = require('../security/JwtAccessTokenManager'); const documentRepository= require('../repositories/DocumentRepository'); const NodemailerRepository= require('../repositories/NodemailerRepository'); +const FollowRepository= require('../repositories/FollowRepository'); function buildBeans() { return { @@ -14,7 +15,8 @@ function buildBeans() { userRepository: new UserRepository(), spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET,process.env.SPOTIFY_AUTH_REDIRECT), documentRepository: new documentRepository(), - mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS) + mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), + followRepository: new FollowRepository() }; } diff --git a/lib/infrastructure/repositories/FollowRepository.js b/lib/infrastructure/repositories/FollowRepository.js new file mode 100644 index 0000000..52ece83 --- /dev/null +++ b/lib/infrastructure/repositories/FollowRepository.js @@ -0,0 +1,64 @@ +'use strict'; + +const sequelize = require('../orm/sequelize/sequelize'); +const user = require('../../domain/model/User'); +const FollowRepository = require('./interfaces/FollowRepositoryAbstract'); +const { Op} = require('sequelize'); +module.exports = class extends FollowRepository { + + constructor() { + super(); + this.db = sequelize; + this.followModel = this.db.model('follow'); + this.artistModel = this.db.model('artiste'); + } + + async follow(userId, artistId) { + let artist = await this.artistModel.findOne({ + where: { + id_artiste: artistId + } + }) + if(!artist){ + artist = await this.artistModel.create({ + id_artiste: artistId, + nb_suivis: 0 + }) + } + artist.nb_suivis = artist.nb_suivis < 0 ? 0 : artist.nb_suivis + artist.save() + const follow = await this.followModel.create({ + id_utilisateur: userId, + id_artiste: artistId + }); + artist.nb_suivis += 1 + artist.save() + await follow.save(); + } + + async unfollow(userId, artistId) { + const artist = await this.artistModel.findOne({ + where: { + id_artiste: artistId + } + }) + await this.followModel.destroy({ + where: { + id_utilisateur: userId, + id_artiste: artistId + } + }) + artist.nb_suivis -= 1 + artist.save() + } + async doesFollows(userId, artistId) { + const follow = await this.followModel.findOne({ + where: { + id_utilisateur: userId, + id_artiste: artistId + } + }) + return !!follow + } +}; + diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index e83612b..77b60c4 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -57,6 +57,9 @@ module.exports = class extends spotifyRepositoryAbstract{ } async getSpotifyArtist(id){ return axios.get(`https://api.spotify.com/v1/artists/${id}`, { + validateStatus: function (status) { + return true; + }, headers: { 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, }, diff --git a/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js new file mode 100644 index 0000000..9f26e7f --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js @@ -0,0 +1,17 @@ +'use strict'; + +module.exports = class { + + follow(userId, artistId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + unfollow(userId, artistId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + doesFollows(userId, artistId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + +}; diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index 8c264ff..9389069 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -73,5 +73,6 @@ module.exports = { }catch(error){ return handleError(error) } - } + }, + } \ No newline at end of file diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index ca7f65f..6e7f34c 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -11,6 +11,7 @@ const sendResetEmail = require("../../application/use_cases/user/sendResetEmail" const resetPassword = require("../../application/use_cases/user/resetPassword") const refreshToken = require("../../application/use_cases/spotify/RefreshToken") const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); +const follow = require("../../application/use_cases/user/follow"); module.exports = { async confirmUser(request, handler) { @@ -115,5 +116,20 @@ module.exports = { catch (e){ return handleError(e) } + }, + async follow(request,handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + console.log(token) + const {artistId} = request.payload + try{ + const returnValue = await follow(token,artistId,serviceLocator) + ? "artiste suivi" + : "vous avez arrêté de suivre l'artiste" + return handler.response(returnValue).code(200) + }catch(error){ + return handleError(error) + } } }; diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index 72b4435..aa9b874 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -155,7 +155,7 @@ module.exports = { query: track } }, - } + }, ]); } }; \ No newline at end of file diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 9358b82..73ae812 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -7,11 +7,10 @@ const { isUser, getUserByConfirmToken, sendResetEmail, - resetPassword + resetPassword, + follow } = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); -const Joi = require('joi') -const validationErrror = require("../../domain/entity/utils/validationError"); const MAX_BYTE_SIZE =20971520 module.exports = { name: 'users', @@ -213,6 +212,33 @@ module.exports = { } } }, + { + method: 'POST', + path: '/users/follow', + handler: UsersController.follow, + options: { + auth: 'jwt', + description: 'follow artist', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: follow + } + }, + } ]); } }; \ No newline at end of file diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 1fcc9bc..f88936e 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -11,7 +11,7 @@ const mockAccesTokenManager = {} const mockSpotifyRepository = {} const mockMailRepository = {} const mockDocumentRepository = {} - +const mockFollowRepository = {} mockAccesTokenManager.generate = ((test) =>{return ''}) @@ -27,7 +27,8 @@ describe('user route', () => { accessTokenManager:mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, mailRepository: mockMailRepository, - documentRepository: mockDocumentRepository + documentRepository: mockDocumentRepository, + followRepository: mockFollowRepository } server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); @@ -431,4 +432,65 @@ describe('user route', () => { expect(res.statusCode).toBe(400); }) }) + describe("/users/follow", ()=>{ + + it("should return valid code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => "something") + mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>"something") + mockFollowRepository.doesFollows = jest.fn(()=> true) + mockFollowRepository.follow = jest.fn(()=> {}) + mockFollowRepository.unfollow = jest.fn(()=> {}) + const res = await server.inject({ + method: 'POST', + url: '/users/follow', + payload: { + artistId: "eztgergrehre", + }, + headers: { + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + } + }) + expect(res.statusCode).toBe(200); + }) + it("should return error code 401",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => null) + + const res = await server.inject({ + method: 'POST', + url: '/users/follow', + payload: { + artistId: "eztgergrehre", + }, + headers: { + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + } + }) + expect(res.statusCode).toBe(401); + }) + it("should return return invalid code 415",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => "something") + mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>{ + return { + error: { + status:415, + message: "message" + } + } + }) + const res = await server.inject({ + method: 'POST', + url: '/users/follow', + payload: { + artistId: "eztgergrehre", + }, + headers: { + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + } + }) + expect(res.statusCode).toBe(415); + }) + }) }); diff --git a/test/unit/application/usecase/spotify/follow.test.js b/test/unit/application/usecase/spotify/follow.test.js new file mode 100644 index 0000000..f74b00a --- /dev/null +++ b/test/unit/application/usecase/spotify/follow.test.js @@ -0,0 +1,59 @@ +const follow = require("../../../../../lib/application/use_cases/user/follow") +const mockUserRepository = {} +const mockAccesTokenManager = {} +const mockSpotifyRepository = {} +const mockFollowRepository = {} +const catchError = require("../utils/catchError") +const serviceLocator = { + userRepository: mockUserRepository, + accessTokenManager:mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + followRepository: mockFollowRepository +} +describe("follow use case", ()=>{ + it("should return false",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => "something") + mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>"something") + mockFollowRepository.doesFollows = jest.fn(()=> true) + mockFollowRepository.follow = jest.fn(()=> {}) + mockFollowRepository.unfollow = jest.fn(()=> {}) + const result = await follow("something","something",serviceLocator) + expect(result).toBe(false); + }) + it("should return true",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => "something") + mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>"something") + mockFollowRepository.doesFollows = jest.fn(()=> false) + mockFollowRepository.follow = jest.fn(()=> {}) + mockFollowRepository.unfollow = jest.fn(()=> {}) + const result = await follow("something","something",serviceLocator) + expect(result).toBe(true); + }) + it("should return error code 401",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => null) + + const result = await catchError(async ()=>{ + await follow("something","something",serviceLocator) + }) + expect(result.code).toBe(401); + }) + it("should return return invalid code 415",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => "something") + mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>{ + return { + error: { + status:415, + message: "message" + } + } + }) + const result = await catchError(async ()=>{ + await follow("something","something",serviceLocator) + }) + expect(result.code).toBe(415); + }) +}) \ No newline at end of file From d68a4f5567895940ed3bf77cba38d1a4fa860706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Sun, 21 Jan 2024 00:35:46 +0100 Subject: [PATCH 20/67] search & test correction --- .../use_cases/security/GetAccessToken.js | 3 +- lib/application/use_cases/spotify/Search.js | 6 +- .../serializers/SerializeSearchItem.js | 34 +++++++++ test/integration/users.test.js | 6 +- .../usecase/fixtures/searchFixture.js | 74 +++++++++++++------ 5 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 lib/interfaces/serializers/SerializeSearchItem.js diff --git a/lib/application/use_cases/security/GetAccessToken.js b/lib/application/use_cases/security/GetAccessToken.js index ecbb6a6..0f31fe1 100644 --- a/lib/application/use_cases/security/GetAccessToken.js +++ b/lib/application/use_cases/security/GetAccessToken.js @@ -11,6 +11,7 @@ module.exports = async (email, password, { userRepository, accessTokenManager }) sub: 'my-sub', // needs to match definition above value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user aud: 'urn:audience:test', // needs to match definition above - iss: 'urn:issuer:test' // needs to match definition above + iss: 'urn:issuer:test', // needs to match definition above, + expiresIn: '365d' }); }; diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index a9d7813..3f52819 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -1,11 +1,11 @@ const SerializeTrack = require("../../../interfaces/serializers/TrackSerializer") const SerializeAlbum = require("../../../interfaces/serializers/AlbumSerializer") const SerializeArtist = require("../../../interfaces/serializers/ArtistSerializer") +const SerializeSearchItem = require("../../../interfaces/serializers/SerializeSearchItem") const MAX_USER = 3 module.exports = async (query,filter, limit, {spotifyRepository, userRepository}) =>{ let limitSize = limit let users = [] - console.log(filter) filter = filter.split(",") if(filter.includes("user")){ filter = filter.filter(item => item !== "user"); @@ -32,6 +32,8 @@ module.exports = async (query,filter, limit, {spotifyRepository, userRepository} returnValue.push(...albums) returnValue.push(...artists) returnValue.sort((item1,item2) => (item2.popularity - item1.popularity)) + if(returnValue.length> limitSize) + returnValue = returnValue.splice(0,limitSize) returnValue.push(...users) - return returnValue + return returnValue.map(item => SerializeSearchItem(item)) } \ No newline at end of file diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js new file mode 100644 index 0000000..88b79e4 --- /dev/null +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -0,0 +1,34 @@ +const seralizer = { + 'artist': (item) => { + return { + imageURL: item.images[0].url, + title: item.name, + subtitle: '' + } + + }, + 'album': (item) => { + return { + imageURL: item.images[0].url, + title: item.name, + subtitle: '' + } + }, + 'track': (item) => { + return { + imageURL: item.album.images[0].url, + title: item.name, + subtitle: '' + } + }, + 'user': (item) => { + return { + imageURL: item.photo, + title: item.alias, + subtitle: item.pseudo + } + } +} +module.exports = (item) => { + return seralizer[item.type](item) +} \ No newline at end of file diff --git a/test/integration/users.test.js b/test/integration/users.test.js index f88936e..44d1963 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -448,7 +448,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` } }) expect(res.statusCode).toBe(200); @@ -464,7 +464,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` } }) expect(res.statusCode).toBe(401); @@ -487,7 +487,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJpYXQiOjE3MDU1MjQ5NTl9.NqUp2-1pLN_WXCXQfst5OgL7BYl8-zIcDKBSBJLY30g` + Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` } }) expect(res.statusCode).toBe(415); diff --git a/test/unit/application/usecase/fixtures/searchFixture.js b/test/unit/application/usecase/fixtures/searchFixture.js index 8bc4ccb..5c1ff4f 100644 --- a/test/unit/application/usecase/fixtures/searchFixture.js +++ b/test/unit/application/usecase/fixtures/searchFixture.js @@ -19,36 +19,68 @@ rawTrackWithOneArtistOneAlbum.popularity = 1 expectedAlbumOneArtist.popularity = 3 expectedFixture.popularity = 2 expectedRawTrackWithOneArtistOneAlbum.popularity = 1 -const mockUser = new User( - 1, - 'testPeudo', - 'testEmail@gmail.com', - 'test_alias', - 'testbio', - 'path/to/photo', - 'path/to/photo', - 'passwordtest', - 'spotifyToken', - 'spotifyToken', - 2, - new Date("10-06-2003") -) + const SpotifyRepositoryFixture = { tracks : { items : [rawTrackWithOneArtistOneAlbum]}, albums : {items : [albumRawOneArtist]}, artists : {items : [artistFixture]} } const expectedSearchResult = [ - expectedAlbumOneArtist, - expectedFixture, - expectedRawTrackWithOneArtistOneAlbum, + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "Perdu D'Avance", + }, + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "Orelsan", + }, + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "test_name", + }, ] const expectedSearchResultWithUsers = [ - expectedAlbumOneArtist, - expectedFixture, - expectedRawTrackWithOneArtistOneAlbum, - mockUser + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "Perdu D'Avance", + }, + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "Orelsan", + }, + { + imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", + subtitle: "", + title: "test_name", + }, + { + imageURL: "photo", + subtitle: "pseudo", + title: "alias", + } ] +const mockUser = { + id_utilisateur: "id_utilisateur", + pseudo: "pseudo", + email: "email", + alias: "alias", + photo: "photo", + photo_temporaire: "photo_temporaire", + token: "token", + refresh_token: "refresh_token", + reset_token: "reset_token", + password: "password", + id_role: "id_role", + ban_until: "ban_until", + confirmed: "confirmed", + confirm_token: "confirm_token", + type: "user" +} module.exports = { mockUser, SpotifyRepositoryFixture, From 373f5382d5e0c3ac06f3723dc0eb2dc7c4a8f15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Sun, 28 Jan 2024 16:27:22 +0100 Subject: [PATCH 21/67] quick fix --- lib/interfaces/serializers/SerializeSearchItem.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 88b79e4..2a960dd 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -1,7 +1,7 @@ const seralizer = { 'artist': (item) => { return { - imageURL: item.images[0].url, + imageURL: item.images[0]?.url, title: item.name, subtitle: '' } @@ -9,14 +9,14 @@ const seralizer = { }, 'album': (item) => { return { - imageURL: item.images[0].url, + imageURL: item.images[0]?.url, title: item.name, subtitle: '' } }, 'track': (item) => { return { - imageURL: item.album.images[0].url, + imageURL: item.album.images[0]?.url, title: item.name, subtitle: '' } From 6ed853859f945318571e659158f3ec8ed4e1aac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:36:54 +0100 Subject: [PATCH 22/67] correction --- lib/application/use_cases/spotify/GetAuthURL.js | 2 +- test/integration/users.test.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/application/use_cases/spotify/GetAuthURL.js b/lib/application/use_cases/spotify/GetAuthURL.js index 0de2487..07f053d 100644 --- a/lib/application/use_cases/spotify/GetAuthURL.js +++ b/lib/application/use_cases/spotify/GetAuthURL.js @@ -17,6 +17,6 @@ const spotifyScopes = [ 'user-read-recently-played', 'ugc-image-upload' ]; -module.exports = async () => { +module.exports = () => { return encodeURI(`https://accounts.spotify.com/authorize?client_id=${process.env.CLIENT_ID}&response_type=${'code'}&redirect_uri=${encodeURI(process.env.SPOTIFY_AUTH_REDIRECT)}&show_dialog=${'true'}&scope=${spotifyScopes.join(' ')}`) } \ No newline at end of file diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 44d1963..5a21fc1 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -4,6 +4,7 @@ const User = require("../../lib/domain/model/User") const bcrypt = require("bcrypt"); const strategy = require("../../lib/infrastructure/config/strategy"); const Jwt = require("@hapi/jwt"); +const jwt = require("jsonwebtoken"); require('dotenv').config() let server const mockUserRepository = {} @@ -12,7 +13,13 @@ const mockSpotifyRepository = {} const mockMailRepository = {} const mockDocumentRepository = {} const mockFollowRepository = {} - +const mockToken = jwt.sign({ + sub: 'my-sub', // needs to match definition above + value: 1, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user + aud: 'urn:audience:test', // needs to match definition above + iss: 'urn:issuer:test', // needs to match definition above, + expiresIn: '365d' +}, process.env.SECRET_ENCODER) mockAccesTokenManager.generate = ((test) =>{return ''}) describe('user route', () => { @@ -448,7 +455,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` + Authorization: `Bearer ${mockToken}` } }) expect(res.statusCode).toBe(200); @@ -464,7 +471,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` + Authorization: `Bearer ${mockToken}` } }) expect(res.statusCode).toBe(401); @@ -487,7 +494,7 @@ describe('user route', () => { artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1zdWIiLCJ2YWx1ZSI6MSwiYXVkIjoidXJuOmF1ZGllbmNlOnRlc3QiLCJpc3MiOiJ1cm46aXNzdWVyOnRlc3QiLCJleHBpcmVzSW4iOiIzNjVkIiwiaWF0IjoxNzA1NzkzNjUxfQ.rBJRI1sBB8tCkKLEWfLSfb4MVlMy6sxQz9vspfLbcFc` + Authorization: `Bearer ${mockToken}` } }) expect(res.statusCode).toBe(415); From d9feea5546aae6243a7916cc4c1a6ec1b381df1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:02:41 +0100 Subject: [PATCH 23/67] test improvement (#26) --- test/integration/spotify.test.js | 10 ++++++++++ .../usecase/spotify/FetchArtist.test.js | 20 ++++++++++++++++--- .../application/usecase/utils/catchError.js | 1 - 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/test/integration/spotify.test.js b/test/integration/spotify.test.js index 41a854e..1e87d83 100644 --- a/test/integration/spotify.test.js +++ b/test/integration/spotify.test.js @@ -111,6 +111,16 @@ describe('spotify route', () => { }); expect(res1.statusCode).toBe(200); }); + it('should respond code 400 invalidID', async () => { + mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{ + throw new Error('test error') + }) + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtist?query=query`, + }); + expect(res1.statusCode).toBe(400); + }); }) describe('/spotify/track', () => { it("should invalid return code 400", async ()=>{ diff --git a/test/unit/application/usecase/spotify/FetchArtist.test.js b/test/unit/application/usecase/spotify/FetchArtist.test.js index 4214ca1..177a4ab 100644 --- a/test/unit/application/usecase/spotify/FetchArtist.test.js +++ b/test/unit/application/usecase/spotify/FetchArtist.test.js @@ -1,22 +1,36 @@ const fetchArtist = require("../../../../../lib/application/use_cases/spotify/FetchArtist") +const catchError = require("../utils/catchError") const { artistFixture, // reponse pas serialise expectedFixture, // reponse serialise } = require("../fixtures/fetchArtist") const mockSpotifyRepository = {} -mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{ - return artistFixture -}) + const idOrelsan = "4FpJcNgOvIpSBeJgRg3OfN" describe('FetchArtist usecase', () => { it("should return a serialized artist item", async ()=>{ + mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{ + return artistFixture + }) const result = await fetchArtist( // fetchArtist metier, serialize est fait a la fin idOrelsan, {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtist de mockSpotifyRepository expect(result).toEqual(expectedFixture) expect(mockSpotifyRepository.getSpotifyArtist).toHaveBeenCalledWith(idOrelsan) }) + it("should return throw an error 400", async ()=>{ + mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{ + throw new Error('test error') + }) + const error = await catchError(async ()=> { + await fetchArtist( // fetchArtist metier, serialize est fait a la fin + idOrelsan, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtist de mockSpotifyRepository + + }) + expect(error.code).toBe(400) + }) }); \ No newline at end of file diff --git a/test/unit/application/usecase/utils/catchError.js b/test/unit/application/usecase/utils/catchError.js index af929d4..3f78d81 100644 --- a/test/unit/application/usecase/utils/catchError.js +++ b/test/unit/application/usecase/utils/catchError.js @@ -4,6 +4,5 @@ module.exports = async (action) => { }catch (error){ return error } - console.log("dfezfezfzefez") return null } \ No newline at end of file From 1f1191d778c562271c6ade978aed2c74424b78a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:00:05 +0100 Subject: [PATCH 24/67] searchbar changement metier (#27) --- lib/application/use_cases/spotify/Search.js | 5 ++-- .../serializers/SerializeSearchItem.js | 8 +++--- .../usecase/fixtures/searchFixture.js | 26 +++++++++++++------ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index 3f52819..8a5264f 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -24,10 +24,11 @@ module.exports = async (query,filter, limit, {spotifyRepository, userRepository} const albums = searchListRaw?.albums ? searchListRaw?.albums?.items.map(item => SerializeAlbum(item)) : [] - const artists = searchListRaw?.artists ? searchListRaw?.artists?.items.map(item => SerializeArtist(item)) : [] - + if(artists[0]) + artists[0].popularity = 101 + returnValue.push(...tracks) returnValue.push(...albums) returnValue.push(...artists) diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 2a960dd..79e85d9 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -11,14 +11,14 @@ const seralizer = { return { imageURL: item.images[0]?.url, title: item.name, - subtitle: '' + subtitle: item?.artists[0].name } }, 'track': (item) => { return { imageURL: item.album.images[0]?.url, title: item.name, - subtitle: '' + subtitle: item?.artists[0].name } }, 'user': (item) => { @@ -30,5 +30,7 @@ const seralizer = { } } module.exports = (item) => { - return seralizer[item.type](item) + const value = seralizer[item.type](item) + value.type = item.type + return value } \ No newline at end of file diff --git a/test/unit/application/usecase/fixtures/searchFixture.js b/test/unit/application/usecase/fixtures/searchFixture.js index 5c1ff4f..25a01fe 100644 --- a/test/unit/application/usecase/fixtures/searchFixture.js +++ b/test/unit/application/usecase/fixtures/searchFixture.js @@ -11,6 +11,7 @@ const { rawTrackWithOneArtistOneAlbum, expectedRawTrackWithOneArtistOneAlbum } = require("../../../interfaces/serializers/fixtures/trackFixture") +const {tracks} = require("../../../../../lib/domain/model/Album"); albumRawOneArtist.popularity = 3 artistFixture.popularity = 2 @@ -29,39 +30,48 @@ const expectedSearchResult = [ { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "", - title: "Perdu D'Avance", + title: "Orelsan", + type: "artist" }, { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - subtitle: "", - title: "Orelsan", + subtitle: "Orelsan", + title: "Perdu D'Avance", + type: "album" }, + { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - subtitle: "", + subtitle: "Orelsan", title: "test_name", + type: "track" }, ] const expectedSearchResultWithUsers = [ { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "", - title: "Perdu D'Avance", + title: "Orelsan", + type: "artist" }, { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - subtitle: "", - title: "Orelsan", + subtitle: "Orelsan", + title: "Perdu D'Avance", + type: "album" }, + { imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - subtitle: "", + subtitle: "Orelsan", title: "test_name", + type: "track" }, { imageURL: "photo", subtitle: "pseudo", title: "alias", + type: "user" } ] const mockUser = { From 02a9ddbcd4053a40fc8d06f34edac707e67581b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:20:17 +0100 Subject: [PATCH 25/67] Feature/fetch artist songs (#28) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commentaires * création fectch artiste (test) * recuperation infos d'un artiste via son ID * test fetchArtist * debut creation fetchArtistSongs (ne passe pas les tests) * premier test fetchAArstistSong sans formatage * fetch serialized reste les tests * tout marche, reste les tests à faire * tout marche, reste les tests à faire 2 * reste test à corriger et rajouter * tests fetch artist songs --- .../use_cases/spotify/FetchArtistSongs.js | 45 ++ lib/domain/entity/SpotifyEntity.js | 27 +- lib/domain/model/Album.js | 2 +- lib/domain/model/Track.js | 1 + .../repositories/SpotifyRepository.js | 37 +- .../interfaces/SpotifyRepositoryAbstract.js | 4 + .../controllers/SpotifyController.js | 16 +- lib/interfaces/routes/spotify.js | 52 ++- lib/interfaces/serializers/AlbumSerializer.js | 1 + .../serializers/AlbumTrackSerializer.js | 2 +- lib/interfaces/serializers/TrackSerializer.js | 2 +- test/integration/spotify.test.js | 48 ++ .../usecase/spotify/FetchArtist.test.js | 3 + .../usecase/spotify/FetchArtistSongs.test.js | 147 ++++++ .../fixtures/artistAlbumsFixture.js | 432 ++++++++++++++++++ 15 files changed, 784 insertions(+), 35 deletions(-) create mode 100644 lib/application/use_cases/spotify/FetchArtistSongs.js create mode 100644 test/unit/application/usecase/spotify/FetchArtistSongs.test.js create mode 100644 test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js diff --git a/lib/application/use_cases/spotify/FetchArtistSongs.js b/lib/application/use_cases/spotify/FetchArtistSongs.js new file mode 100644 index 0000000..c880d9c --- /dev/null +++ b/lib/application/use_cases/spotify/FetchArtistSongs.js @@ -0,0 +1,45 @@ +const SerializeAlbum = require("../../../interfaces/serializers/AlbumSerializer") +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (id, filter, limit, { spotifyRepository }) => { + const album = "album" + const single = "single" + const compilation = "compilation" + const appearsOn = "appears_on" + + let returnValue = [] + let artistSongs = {} + + try{ + artistSongs = await spotifyRepository.getSpotifyArtistSongs(id, filter, limit) + } + catch(error){ + console.log("18 erreur") + throwStatusCode(400, `l'id : ${id} n'existe pas`) + } + + const filterTab = filter.split(',') + + // Recuperation selon le(s) filtre(s) et serialisation + + //recupere par defaut le include_groups : appears_on avec album + const albumRaw = filterTab.includes(album) ? artistSongs?.items?.filter(item => item?.album_group === album) || [] : []; + const albumSerialized = albumRaw.length > 0 ? albumRaw.map(item => SerializeAlbum(item)) : []; + + const singleRaw = filterTab.includes(single) ? artistSongs?.items?.filter(item => item?.album_group === single) || [] : []; + const singleSerialized = singleRaw.length > 0 ? singleRaw.map(item => SerializeAlbum(item)) : []; + + const compilationRaw = filterTab.includes(compilation) ? artistSongs?.items?.filter(item => item?.album_group === compilation) || [] : []; + const compilationSerialized = compilationRaw.length > 0 ? compilationRaw.map(item => SerializeAlbum(item)) : []; + + // autre champ utilise pour appearsOn : album_group + const appearsOnRaw = filterTab.includes(appearsOn) ? artistSongs?.items?.filter(item => item?.album_group === appearsOn) || [] : []; + const appearsOnialized = appearsOnRaw.length > 0 ? appearsOnRaw.map(item => SerializeAlbum(item)) : []; + + returnValue.push(...albumSerialized) + returnValue.push(...singleSerialized) + returnValue.push(...compilationSerialized) + returnValue.push(...appearsOnialized) + + return returnValue +} \ No newline at end of file diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index 6c968a0..2d4ed97 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -9,7 +9,7 @@ const search = Joi.object().keys({ spotify_filter: Joi .string() .max(50) - .required() + .optional() .custom((value, helpers) =>{ const allowedValues = ["track","artist","album","user"] const tabValue = value.split(",") @@ -42,4 +42,27 @@ const track = Joi.object().keys({ const fetchArtist = Joi.object().keys({ query: Joi.string().min(1).required(), //correspond au ID Artist }) -module.exports = {album,fetchArtist,search,track} \ No newline at end of file + +const fetchArtistSongs = Joi.object().keys({ + id: Joi.string().min(1).required(), + filter: Joi + .string() + .optional() + .default("album,single") + .custom((value, helpers) =>{ + const allowedValues = ["album","single","appears_on","compilation"] + const tabValue = value.replace(/\s/g, '').split(",") // retire les espaces + + // Renvoie une erreur des qu'un filtre n'est pas valide + for (const x of tabValue) { + if (!allowedValues.includes(x)) { + return helpers.error('any.invalid'); + } + } + return tabValue.join(",") + }), + + limit: Joi.number().integer().optional().min(1).max(50) +}) + +module.exports = {album,search,track,fetchArtist,fetchArtistSongs} \ No newline at end of file diff --git a/lib/domain/model/Album.js b/lib/domain/model/Album.js index 3d69208..f0bf662 100644 --- a/lib/domain/model/Album.js +++ b/lib/domain/model/Album.js @@ -10,6 +10,6 @@ module.exports = class { this.artists = album.artists this.tracks = album.tracks this.genres = album.genres - this.type = "album" + this.type = album.type } } \ No newline at end of file diff --git a/lib/domain/model/Track.js b/lib/domain/model/Track.js index 4cecd66..d37127d 100644 --- a/lib/domain/model/Track.js +++ b/lib/domain/model/Track.js @@ -8,5 +8,6 @@ module.exports = class { this.popularity = track.popularity this.spotify_url = track.spotify_url this.type = "track" + } } \ No newline at end of file diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index 77b60c4..ec5c867 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -26,7 +26,8 @@ module.exports = class extends spotifyRepositoryAbstract{ }) } - async getSpotifySearchList(query,filter,limit){ + + async getSpotifySearchList(query,filter,limit){ return axios.get('https://api.spotify.com/v1/search', { headers: { 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, @@ -37,9 +38,20 @@ module.exports = class extends spotifyRepositoryAbstract{ limit : limit }, }) - .then((response) => { - return response.data - }) + .then((response) => { + return response.data + }) + } + + async getSpotifyArtist(id){ + return axios.get(`https://api.spotify.com/v1/artists/${id}`, { + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, + }) + .then((response) => { + return response.data + }) } async getSpotifyAlbums(id){ @@ -130,4 +142,21 @@ module.exports = class extends spotifyRepositoryAbstract{ return response.data }) } + async getSpotifyArtistSongs(id, filter, limit) { + return axios.get(`https://api.spotify.com/v1/artists/${id}/albums`, { + validateStatus: function (status) { + return true; // axios traite les reponses tjs en reussites + }, + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, + params: { + album_type: filter, // a gauche le champs de l'API Spotify + limit: limit + }, + }) + .then((response) => { + return response.data + }) + } }; diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 6b7d59d..595763d 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -9,6 +9,10 @@ module.exports = class { async getSpotifySearchList(query,filter,limit){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + + async getSpotifyArtist(id){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } refreshToken(token) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index 9389069..c1633e5 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -5,6 +5,8 @@ const fetchArtist = require("../../application/use_cases/spotify/FetchArtist"); const getTrack = require("../../application/use_cases/spotify/getTrack"); const getSearchFilters = require("../../application/use_cases/spotify/getSearchFilters"); const handleError = require("./utils/handleError"); +const fetchArtistSongs = require("../../application/use_cases/spotify/FetchArtistSongs"); + module.exports = { @@ -66,6 +68,17 @@ module.exports = { return handleError(error) } }, + async fetchArtistSongs(request,handler){ + try { + const serviceLocator = request.server.app.serviceLocator; + let {id, filter, limit} = request.query + const fetchArtistSongsResult = await fetchArtistSongs(id, filter, limit, serviceLocator) + return handler.response(fetchArtistSongsResult).code(200) + }catch(error){ + return handleError(error) + } + }, + async getAuthURL(request,handler) { try{ const authURL = await getAuthURL() @@ -73,6 +86,5 @@ module.exports = { }catch(error){ return handleError(error) } - }, - + } } \ No newline at end of file diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index aa9b874..c5028ad 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -1,4 +1,4 @@ -const {search, album, track,fetchArtist} = require("../../domain/entity/SpotifyEntity") +const {album, trackBody,track,fetchArtist, fetchArtistSongs } = require("../../domain/entity/SpotifyEntity") const spotify = require("../controllers/SpotifyController") module.exports = { name: 'spotify', @@ -11,7 +11,7 @@ module.exports = { path: '/spotify/search', handler: spotify.search, options: { - description: 'get a spotify search', + description: 'get a spotify track', tags: ['api'], plugins: { 'hapi-swagger': { @@ -28,16 +28,16 @@ module.exports = { } }, validate: { - query: search + query: trackBody } }, }, { method: 'GET', - path: '/spotify/Searchfilters', - handler: spotify.getSearchFilters, + path: '/spotify/fetchArtist', + handler: spotify.fetchArtist, options: { - description: 'get search filters', + description: 'get Artist informations', tags: ['api'], plugins: { 'hapi-swagger': { @@ -53,14 +53,17 @@ module.exports = { } } }, + validate: { + query: fetchArtist + } }, }, { method: 'GET', - path: '/spotify/fetchArtist', - handler: spotify.fetchArtist, + path: '/spotify/fetchArtistSongs', + handler: spotify.fetchArtistSongs, options: { - description: 'get Artist informations', + description: 'get Artist Songs informations', tags: ['api'], plugins: { 'hapi-swagger': { @@ -77,16 +80,16 @@ module.exports = { } }, validate: { - query: fetchArtist + query: fetchArtistSongs } }, }, { method: 'GET', - path: '/spotify/getAuthURL', - handler: spotify.getAuthURL, + path: '/spotify/album', + handler: spotify.getAlbums, options: { - description: 'get auth URL', + description: 'get a spotify album', tags: ['api'], plugins: { 'hapi-swagger': { @@ -102,14 +105,17 @@ module.exports = { } } }, + validate: { + query: album + } }, }, { method: 'GET', - path: '/spotify/album', - handler: spotify.getAlbums, + path: '/spotify/track', + handler: spotify.getTracks, options: { - description: 'get a spotify album', + description: 'get a spotify track', tags: ['api'], plugins: { 'hapi-swagger': { @@ -126,16 +132,17 @@ module.exports = { } }, validate: { - query: album + query: track } }, }, + { method: 'GET', - path: '/spotify/track', - handler: spotify.getTracks, + path: '/spotify/getAuthURL', + handler: spotify.getAuthURL, options: { - description: 'get a spotify track', + description: 'get auth URL', tags: ['api'], plugins: { 'hapi-swagger': { @@ -151,11 +158,8 @@ module.exports = { } } }, - validate: { - query: track - } }, - }, + } ]); } }; \ No newline at end of file diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index cdccd6f..1ec7207 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -14,6 +14,7 @@ const serializeAlbum = (albumRaw) => { artists : albumRaw?.artists?.map(item => SerializeArtist(item)), tracks: tracks, genres : albumRaw?.genres, + type : albumRaw?.album_group ? albumRaw?.album_group : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), }; return new Album(album) diff --git a/lib/interfaces/serializers/AlbumTrackSerializer.js b/lib/interfaces/serializers/AlbumTrackSerializer.js index 8be5481..47dc35d 100644 --- a/lib/interfaces/serializers/AlbumTrackSerializer.js +++ b/lib/interfaces/serializers/AlbumTrackSerializer.js @@ -8,7 +8,7 @@ const serializeTrack = (trackRaw) => { name: trackRaw.name, album: undefined, artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, - spotify_url : trackRaw.external_urls.spotify, + spotify_url : trackRaw?.external_urls.spotify, duration_ms: trackRaw.duration_ms, popularity: undefined, }; diff --git a/lib/interfaces/serializers/TrackSerializer.js b/lib/interfaces/serializers/TrackSerializer.js index 283c9b1..f20ac5e 100644 --- a/lib/interfaces/serializers/TrackSerializer.js +++ b/lib/interfaces/serializers/TrackSerializer.js @@ -11,7 +11,7 @@ const serializeTrack = (trackRaw) => { album: album, artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, spotify_url : trackRaw.external_urls.spotify, - duration_ms: trackRaw.duration_ms, + duration_ms: trackRaw?.duration_ms, popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, }; return new Track(track) diff --git a/test/integration/spotify.test.js b/test/integration/spotify.test.js index 1e87d83..aca93dd 100644 --- a/test/integration/spotify.test.js +++ b/test/integration/spotify.test.js @@ -17,6 +17,7 @@ const mockSpotifyRepository = {} mockUserRepository.getUsersByPseudo = jest.fn((pseudo) =>{return []}) mockSpotifyRepository.getSpotifySearchList = jest.fn((query, filter, limitSize) =>{return {}}) mockSpotifyRepository.getSpotifyArtist = jest.fn((id) =>{return {}}) // mock la fct de repo +mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{return {}}) describe('spotify route', () => { @@ -168,4 +169,51 @@ describe('spotify route', () => { }) }); + + + describe("/spotify/FetchArtistSongs", ()=>{ + + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtistSongs?id=azerty1234`, // juste id, filter, limit optional + }); + expect(res1.statusCode).toBe(200); + }); + + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtistSongs?id=azerty1234&limit=10`, // juste id et limit + }); + expect(res1.statusCode).toBe(200); + }); + + it('should respond code 200', async () => { + const res1 = await server.inject({ + method: 'GET', + url: '/spotify/fetchArtistSongs?id=azerty1234&filter= album , compilation , appears_on ' // id et filtre avec espaces + }); + expect(res1.statusCode).toBe(200); + }); + + it('should respond code 400 invalid id', async () => { + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtistSongs?id=`, + }); + expect(res1.statusCode).toBe(400); + }); + + it('should respond code 400 invalidID', async () => { + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id) =>{ + throw new Error('test error') + }) + const res1 = await server.inject({ + method: 'GET', + url: `/spotify/fetchArtistSongs?id=1234`, + }); + expect(res1.statusCode).toBe(400); + }); + }) }); diff --git a/test/unit/application/usecase/spotify/FetchArtist.test.js b/test/unit/application/usecase/spotify/FetchArtist.test.js index 177a4ab..6099c08 100644 --- a/test/unit/application/usecase/spotify/FetchArtist.test.js +++ b/test/unit/application/usecase/spotify/FetchArtist.test.js @@ -18,6 +18,9 @@ describe('FetchArtist usecase', () => { const result = await fetchArtist( // fetchArtist metier, serialize est fait a la fin idOrelsan, {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtist de mockSpotifyRepository + console.log("result", result) + console.log("expectedFixture", expectedFixture) + expect(result).toEqual(expectedFixture) expect(mockSpotifyRepository.getSpotifyArtist).toHaveBeenCalledWith(idOrelsan) }) diff --git a/test/unit/application/usecase/spotify/FetchArtistSongs.test.js b/test/unit/application/usecase/spotify/FetchArtistSongs.test.js new file mode 100644 index 0000000..16d1f1a --- /dev/null +++ b/test/unit/application/usecase/spotify/FetchArtistSongs.test.js @@ -0,0 +1,147 @@ +const { VERSION } = require("underscore"); +const catchError = require("../utils/catchError") + +const FetchArtistSongs = require("../../../../../lib/application/use_cases/spotify/FetchArtistSongs") +const { + songsRawAlbumArtist, + expectedAlbumArtist, + songsRawSingleArtist, + expectedSingleArtist, + songsRawCompilationArtist, + expectedCompilationArtist, + songsRawAppearsOnArtist, + expectedAppearsOnArtist +} = require("../../../interfaces/serializers/fixtures/artistAlbumsFixture") + +const songsRawAll = { + items: [ + songsRawAlbumArtist.items[0], + songsRawSingleArtist.items[0], + songsRawCompilationArtist.items[0], + songsRawAppearsOnArtist.items[0] + ], + } + + const expectedFixturesAll = [ + expectedAlbumArtist[0], + expectedSingleArtist[0], + expectedCompilationArtist[0], + expectedAppearsOnArtist[0] + ]; + + const songsRawAlbumSingle = { + items: [ + songsRawAlbumArtist.items[0], + songsRawSingleArtist.items[0] + ] + } + + const expectedFixturesAlbumSingle = [ + expectedAlbumArtist[0], + expectedSingleArtist[0] + ]; + + +const mockSpotifyRepository = {} + +const id = "57TzZhbqvYoUBzJSVKFVlG" +const album ="album" +const single ="single" +const compilation ="compilation" +const appearsOn = "appears_on" + + +describe('FetchArtistSongs usecase', () => { + it("should return a serialized album item", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawAlbumArtist + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + album, + 1, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedAlbumArtist) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id, album, 1) + + }) + + it("should return a serialized album type single item", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawSingleArtist + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + single, + 1, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedSingleArtist) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id, single, 1) + }) + + + + it("should return a serialized album type compilation item", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawCompilationArtist + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + compilation, + 1, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedCompilationArtist) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id, compilation, 1) + }) + + it("should return a serialized album type appears_on item", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawAppearsOnArtist + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + appearsOn, + 1, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedAppearsOnArtist) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id, appearsOn, 1) + }) + + it("should return 4 serialized album's type album, single, compilation and appears_on items", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawAll + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + "album,single,compilation,appears_on", + 4, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedFixturesAll) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id, "album,single,compilation,appears_on" , 4) + }) + + it("should return 2 serialized album's type album and single", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + return songsRawAlbumSingle + }) + const result = await FetchArtistSongs( // fetchArtist metier, serialize est fait a la fin + id, + "album,single", + 2, + {spotifyRepository : mockSpotifyRepository}) // va utiliser la fct getSpotifyArtistSongs de mockSpotifyRepository + expect(result).toEqual(expectedFixturesAlbumSingle) + expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id,"album,single" , 2) + }) + +console.log("ONE PIECE") + it("should return throw an error 400", async ()=>{ + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ + throw new Error('test error') + }) + + const error = await catchError(async ()=> { + await FetchArtistSongs(id, "single", 1, {spotifyRepository : mockSpotifyRepository}) + }) + expect(error.code).toBe(400) + }) +}); \ No newline at end of file diff --git a/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js b/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js new file mode 100644 index 0000000..f5cea4d --- /dev/null +++ b/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js @@ -0,0 +1,432 @@ + +/* Artist */ + +const artistFixture = { + external_urls: { + spotify: "https://open.spotify.com/artist/3fMbdgg4jU18AjLCKBhRSm" + }, + id: "3fMbdgg4jU18AjLCKBhRSm", + name: "Michael Jackson", + popularity: 81, + genres: ["r&b", "soul"], + "images": [ + { + "url": "https://i.scdn.co/image/ab6761610000e5eb0e08ea2c4d6789fbf5cbe0aa", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/ab676161000051740e08ea2c4d6789fbf5cbe0aa", + "height": 320, + "width": 320 + }, + { + "url": "https://i.scdn.co/image/ab6761610000f1780e08ea2c4d6789fbf5cbe0aa", + "height": 160, + "width": 160 + } + ] +} + +const artistExpectedFixture = { + id: "3fMbdgg4jU18AjLCKBhRSm", + name: "Michael Jackson", + images: [ + { + "url": "https://i.scdn.co/image/ab6761610000e5eb0e08ea2c4d6789fbf5cbe0aa", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/ab676161000051740e08ea2c4d6789fbf5cbe0aa", + "height": 320, + "width": 320 + }, + { + "url": "https://i.scdn.co/image/ab6761610000f1780e08ea2c4d6789fbf5cbe0aa", + "height": 160, + "width": 160 + } + ], + popularity: 81, + genres: ["r&b", "soul"], + spotify_url: "https://open.spotify.com/artist/3fMbdgg4jU18AjLCKBhRSm", + type: "artist", +} + + + + +/* Pour appearsOn */ +const artistFixtureAppearsOn = { + external_urls: { + spotify: "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" + }, + id: "3TVXtAsR1Inumwj472S9r4", + name: "Drake", + popularity: 97, + genres: ["canadian hip hop","canadian pop","hip hop","pop rap","rap"], + "images": [ + { + "url": "https://i.scdn.co/image/ab6761610000e5eb4293385d324db8558179afd9", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/ab676161000051744293385d324db8558179afd9", + "height": 320, + "width": 320 + }, + { + "url": "https://i.scdn.co/image/ab6761610000f1784293385d324db8558179afd9", + "height": 160, + "width": 160 + } + ] +} + +const artistExpectedFixtureAppearsOn = { + id: "3TVXtAsR1Inumwj472S9r4", + name: "Drake", + images: [ + { + "url": "https://i.scdn.co/image/ab6761610000e5eb4293385d324db8558179afd9", + "height": 640, + "width": 640 + }, + { + "url": "https://i.scdn.co/image/ab676161000051744293385d324db8558179afd9", + "height": 320, + "width": 320 + }, + { + "url": "https://i.scdn.co/image/ab6761610000f1784293385d324db8558179afd9", + "height": 160, + "width": 160 + } + ], + popularity: 97, + genres: ["canadian hip hop","canadian pop","hip hop","pop rap","rap"], + spotify_url: "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4", + type: "artist", +} + + +/* Album Raw */ + +const songsRawAlbumArtist = { + items: [ + { + album_type: 'album', + total_tracks: 34, + popularity: 50, // rentre a la main + external_urls: { + spotify: "https://open.spotify.com/album/57TzZhbqvYoUBzJSVKFVlG" + }, + id: "57TzZhbqvYoUBzJSVKFVlG", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273822e06488f98e53e8743ff6b", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02822e06488f98e53e8743ff6b", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851822e06488f98e53e8743ff6b", + "width": 64 + } + ], + name: "Thriller 40", + release_date: "2022-11-18", + artists: [ + artistFixture + ], + album_group: "album" + } + ] +} + +/* Expected Album Raw */ + +const expectedAlbumArtist = [ + { + id: "57TzZhbqvYoUBzJSVKFVlG", + total_tracks: 34, + name: "Thriller 40", + popularity: 50, // rentre a la main + release_date: "2022-11-18", + spotify_url: "https://open.spotify.com/album/57TzZhbqvYoUBzJSVKFVlG", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273822e06488f98e53e8743ff6b", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02822e06488f98e53e8743ff6b", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851822e06488f98e53e8743ff6b", + "width": 64 + } + ], + artists: [ + artistExpectedFixture + ], + tracks: undefined, + genres: undefined, + type: "album" + } +] + + + +/* Single Raw */ + +const songsRawSingleArtist = { + items: [ + { + album_type: 'single', + total_tracks: 1, + popularity: 50, // rentre a la main + external_urls: { + spotify: "https://open.spotify.com/album/6ST7naJFCe9iBeOleU5Ccu" + }, + id: "6ST7naJFCe9iBeOleU5Ccu", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2739e9ceac980e4b6b59a766f8b", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e029e9ceac980e4b6b59a766f8b", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d000048519e9ceac980e4b6b59a766f8b", + "width": 64 + } + ], + name: "Michael Jackson x Mark Ronson: Diamonds are Invincible", + release_date: "2018-08-29", + artists: [ + artistFixture + ], + album_group: "single" + } + ] +} + +/* Expected Single Raw */ + +const expectedSingleArtist = [ + { + id: "6ST7naJFCe9iBeOleU5Ccu", + total_tracks: 1, + name: "Michael Jackson x Mark Ronson: Diamonds are Invincible", + popularity: 50, // rentre a la main + release_date: "2018-08-29", + spotify_url: "https://open.spotify.com/album/6ST7naJFCe9iBeOleU5Ccu", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2739e9ceac980e4b6b59a766f8b", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e029e9ceac980e4b6b59a766f8b", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d000048519e9ceac980e4b6b59a766f8b", + "width": 64 + } + ], + artists: [ + artistExpectedFixture + ], + tracks: undefined, + genres: undefined, + type: "single" + } +] + + +/* Compilation Raw */ + +const songsRawCompilationArtist = { + items: [ + { + album_type: 'compilation', + total_tracks: 14, + popularity: 50, // rentre a la main + external_urls: { + spotify: "https://open.spotify.com/album/2X8UOIkZQdcz2Hi5Ynt2uk" + }, + id: "2X8UOIkZQdcz2Hi5Ynt2uk", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273cde37cfdee48dc0eae1e2ab8", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02cde37cfdee48dc0eae1e2ab8", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851cde37cfdee48dc0eae1e2ab8", + "width": 64 + } + ], + name: "Scream", + release_date: "2017-09-27", + artists: [ + artistFixture + ], + album_group: "compilation" + } + ] +} + +/* Expected Compilation Raw */ + +const expectedCompilationArtist = [ + { + id: "2X8UOIkZQdcz2Hi5Ynt2uk", + total_tracks: 14, + name: "Scream", + popularity: 50, // rentre a la main + release_date: "2017-09-27", //fait + spotify_url: "https://open.spotify.com/album/2X8UOIkZQdcz2Hi5Ynt2uk", + images: [ //fait + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273cde37cfdee48dc0eae1e2ab8", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02cde37cfdee48dc0eae1e2ab8", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851cde37cfdee48dc0eae1e2ab8", + "width": 64 + } + ], + artists: [ + artistExpectedFixture + ], + tracks: undefined, + genres: undefined, + type: "compilation" + } +] + + + +/* appears_on Raw */ + +const songsRawAppearsOnArtist = { + items: [ + { + album_type: 'album', + total_tracks: 25, + popularity: 50, // rentre a la main + external_urls: { + spotify: "https://open.spotify.com/album/1ATL5GLyefJaxhQzSPVrLX" + }, + id: "1ATL5GLyefJaxhQzSPVrLX", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273f907de96b9a4fbc04accc0d5", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02f907de96b9a4fbc04accc0d5", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851f907de96b9a4fbc04accc0d5", + "width": 64 + } + ], + name: "Scorpion", + release_date: "2018-06-29", + artists: [ + artistFixtureAppearsOn + ], + album_group: "appears_on" + } + ] +} + +/* Expected appears_on Raw */ + +const expectedAppearsOnArtist = [ + { + id: "1ATL5GLyefJaxhQzSPVrLX", + total_tracks: 25, + name: "Scorpion", + popularity: 50, // rentre a la main + release_date: "2018-06-29", //fait + spotify_url: "https://open.spotify.com/album/1ATL5GLyefJaxhQzSPVrLX", + images: [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b273f907de96b9a4fbc04accc0d5", + "width": 640 + }, + { + "height": 300, + "url": "https://i.scdn.co/image/ab67616d00001e02f907de96b9a4fbc04accc0d5", + "width": 300 + }, + { + "height": 64, + "url": "https://i.scdn.co/image/ab67616d00004851f907de96b9a4fbc04accc0d5", + "width": 64 + } + ], + artists: [ + artistExpectedFixtureAppearsOn + ], + tracks: undefined, + genres: undefined, + type: "appears_on" + } +] + + + + +module.exports = { + songsRawAlbumArtist, + expectedAlbumArtist, + songsRawSingleArtist, + expectedSingleArtist, + songsRawCompilationArtist, + expectedCompilationArtist, + songsRawAppearsOnArtist, + expectedAppearsOnArtist +} \ No newline at end of file From c0c1b4c4a42e59dda5bd480a2feae1c9341075d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:31:50 +0100 Subject: [PATCH 26/67] correction (#29) --- lib/domain/entity/SpotifyEntity.js | 2 +- lib/interfaces/routes/spotify.js | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index 2d4ed97..b3d932c 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -9,7 +9,7 @@ const search = Joi.object().keys({ spotify_filter: Joi .string() .max(50) - .optional() + .required() .custom((value, helpers) =>{ const allowedValues = ["track","artist","album","user"] const tabValue = value.split(",") diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index c5028ad..3669e62 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -1,4 +1,4 @@ -const {album, trackBody,track,fetchArtist, fetchArtistSongs } = require("../../domain/entity/SpotifyEntity") +const {album, search,track,fetchArtist, fetchArtistSongs } = require("../../domain/entity/SpotifyEntity") const spotify = require("../controllers/SpotifyController") module.exports = { name: 'spotify', @@ -28,10 +28,33 @@ module.exports = { } }, validate: { - query: trackBody + query: search } }, }, + { + method: 'GET', + path: '/spotify/Searchfilters', + handler: spotify.getSearchFilters, + options: { + description: 'get spotify search filters', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: { description: 'Success' }, + 204: { description: 'No content' }, + 401: { description: 'Unauthorized' }, + 403: { description: 'forbidden' }, + 404: { description: 'Ressource not found' }, + 500: { description: 'Internal server error' }, + 502: { description: 'bad gateway' }, + 503: { description: 'Service unavailable' }, + } + } + }, + }, + }, { method: 'GET', path: '/spotify/fetchArtist', From 3a78b0ee4f5c5ac358b3507f9319294ae843c268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:46:31 +0100 Subject: [PATCH 27/67] re ajout test fetch artist songs (#31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Léa Thai --- test/integration/spotify.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/integration/spotify.test.js b/test/integration/spotify.test.js index aca93dd..c77aa1c 100644 --- a/test/integration/spotify.test.js +++ b/test/integration/spotify.test.js @@ -197,6 +197,15 @@ describe('spotify route', () => { expect(res1.statusCode).toBe(200); }); + it('should respond code 400', async () => { + const res1 = await server.inject({ + method: 'GET', + url: '/spotify/fetchArtistSongs?id=azerty1234&filter= albu , compilation , appears_on ' // mauvaise orthographe sur un filtre + }); + expect(res1.statusCode).toBe(400); + }); + + it('should respond code 400 invalid id', async () => { const res1 = await server.inject({ method: 'GET', From 178e98ab4132bf5d76cc9bdc537f78d8e866e130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:05:56 +0100 Subject: [PATCH 28/67] added single to serialisation (#32) --- lib/interfaces/serializers/SerializeSearchItem.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 79e85d9..fab48b9 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -21,6 +21,13 @@ const seralizer = { subtitle: item?.artists[0].name } }, + 'single': (item) => { + return { + imageURL: item.images[0]?.url, + title: item.name, + subtitle: item?.artists[0].name + } + }, 'user': (item) => { return { imageURL: item.photo, From a6310e71a946b89fed8dc7d2542b3f21f9d0c762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Tue, 6 Feb 2024 14:49:44 +0100 Subject: [PATCH 29/67] added compilation to serilization --- lib/interfaces/serializers/SerializeSearchItem.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index fab48b9..8c6a6c1 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -28,6 +28,13 @@ const seralizer = { subtitle: item?.artists[0].name } }, + 'compilation': (item) => { + return { + imageURL: item.images[0]?.url, + title: item.name, + subtitle: item?.artists[0].name + } + }, 'user': (item) => { return { imageURL: item.photo, From 9b654c9e5bbd948a8ec740b6ff7ed79e9571ea4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 12 Feb 2024 00:24:57 +0100 Subject: [PATCH 30/67] Fix/auth sytem (#33) * searchbar changement metier * new endpoint * usecase and test correction * added redirect uri * added mobile arg to getAuthURL * changed token generation * test for redirect * modified auth with spotify * removed getAuthURL * new endpoint * usecase and test correction * added redirect uri * added mobile arg to getAuthURL * changed token generation * test for redirect * modified auth with spotify * removed getAuthURL * remove scheme --- .../use_cases/security/GetAccessToken.js | 8 +- .../use_cases/spotify/GetAuthURL.js | 22 --- .../use_cases/user/AuthWithSpotify.js | 57 +++++++ .../use_cases/user/CompleteAccount.js | 18 +-- lib/application/use_cases/user/CreateUser.js | 29 ++-- .../use_cases/user/sendResetEmail.js | 6 +- lib/domain/entity/UserEntity.js | 12 +- lib/infrastructure/config/service-locator.js | 2 +- .../repositories/SpotifyRepository.js | 7 +- .../repositories/UserRepository.js | 7 + .../interfaces/SpotifyRepositoryAbstract.js | 2 +- .../interfaces/UserRepositoryAbstract.js | 4 + .../security/JwtAccessTokenManager.js | 10 +- lib/infrastructure/webserver/server.js | 2 +- .../controllers/SpotifyController.js | 10 -- lib/interfaces/controllers/UsersController.js | 29 +++- lib/interfaces/routes/spotify.js | 25 +-- lib/interfaces/routes/users.js | 24 ++- test/integration/users.test.js | 153 +++++++++++++----- .../usecase/user/authWithSpotify.test.js | 138 ++++++++++++++++ .../usecase/user/completeAccount.test.js | 44 ++--- .../usecase/user/createUser.test.js | 45 +----- 22 files changed, 425 insertions(+), 229 deletions(-) delete mode 100644 lib/application/use_cases/spotify/GetAuthURL.js create mode 100644 lib/application/use_cases/user/AuthWithSpotify.js create mode 100644 test/unit/application/usecase/user/authWithSpotify.test.js diff --git a/lib/application/use_cases/security/GetAccessToken.js b/lib/application/use_cases/security/GetAccessToken.js index 0f31fe1..dd98e83 100644 --- a/lib/application/use_cases/security/GetAccessToken.js +++ b/lib/application/use_cases/security/GetAccessToken.js @@ -7,11 +7,5 @@ module.exports = async (email, password, { userRepository, accessTokenManager }) throwStatusCode(401,'Bad credentials') } - return accessTokenManager.generate({ - sub: 'my-sub', // needs to match definition above - value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user - aud: 'urn:audience:test', // needs to match definition above - iss: 'urn:issuer:test', // needs to match definition above, - expiresIn: '365d' - }); + return accessTokenManager.generate(user); }; diff --git a/lib/application/use_cases/spotify/GetAuthURL.js b/lib/application/use_cases/spotify/GetAuthURL.js deleted file mode 100644 index 07f053d..0000000 --- a/lib/application/use_cases/spotify/GetAuthURL.js +++ /dev/null @@ -1,22 +0,0 @@ -const spotifyScopes = [ - 'user-read-private', - 'user-read-email', - 'user-library-read', - 'playlist-read-private', - 'playlist-read-collaborative', - 'playlist-modify-public', - 'playlist-modify-private', - 'user-read-playback-state', - 'user-modify-playback-state', - 'user-read-currently-playing', - 'user-follow-read', - 'user-follow-modify', - 'user-library-read', - 'user-library-modify', - 'user-top-read', - 'user-read-recently-played', - 'ugc-image-upload' -]; -module.exports = () => { - return encodeURI(`https://accounts.spotify.com/authorize?client_id=${process.env.CLIENT_ID}&response_type=${'code'}&redirect_uri=${encodeURI(process.env.SPOTIFY_AUTH_REDIRECT)}&show_dialog=${'true'}&scope=${spotifyScopes.join(' ')}`) -} \ No newline at end of file diff --git a/lib/application/use_cases/user/AuthWithSpotify.js b/lib/application/use_cases/user/AuthWithSpotify.js new file mode 100644 index 0000000..e719062 --- /dev/null +++ b/lib/application/use_cases/user/AuthWithSpotify.js @@ -0,0 +1,57 @@ +'use strict'; + +const User = require('../../../domain/model/User'); +const throwStatusCode = require("../utils/throwStatusCode") +const crypto = require("crypto"); +const bcrypt = require("bcrypt"); +module.exports = async (spotify_code, callback, { userRepository,spotifyRepository,accessTokenManager}) => { + const {access_token, refresh_token,error} = await spotifyRepository.getToken(spotify_code,callback) + if(error){ + throwStatusCode(400,error.message) + } + console.log("peut etres") + const {email,display_name,images} = await spotifyRepository.getAccountData(access_token) + const image = images?.at(-1)?.url + const userTest = await userRepository.getByEmailOrPseudo(email,email) + if(userTest?.confirmed && userTest?.refresh_token){ + userTest.token = refresh_token + userTest.refresh_token = refresh_token + await userRepository.updateUser(userTest) + return { + email : email, + token: accessTokenManager.generate(userTest) + } + } + if(!userTest){ + // const password = await bcrypt.hash(crypto.randomBytes(16).toString('hex'),10) + // if(!password) { + // throwStatusCode('500','Internal server error') + // } + const confirm_token = crypto.randomBytes(16).toString('hex') + const userRaw = { + email, + alias: display_name ? display_name : null, + photo: image ? image : null, + confirmed:false, + token: access_token, + // password, + refresh_token, + confirm_token + } + const user = new User( + userRaw); + await userRepository.persist(user) + setTimeout(()=>{ + userRepository.removeUserByConfirmToken(confirm_token) + },3600*1000*24) + return { + confirmToken: confirm_token, + } + } + if(!userTest.refresh_token || !userTest.confirm_token){ + throwStatusCode(403, 'erreur') + } + return { + confirmToken: userTest.confirm_token + } +}; diff --git a/lib/application/use_cases/user/CompleteAccount.js b/lib/application/use_cases/user/CompleteAccount.js index 33951f7..f9dcac4 100644 --- a/lib/application/use_cases/user/CompleteAccount.js +++ b/lib/application/use_cases/user/CompleteAccount.js @@ -1,14 +1,8 @@ 'use strict'; -const User = require('../../../domain/model/User'); -const bcrypt = require("bcrypt"); -const rolesEnum = require('../../../domain/model/utils/RolesEnum') + const throwStatusCode = require("../utils/throwStatusCode") -module.exports = async (pseudo,alias, bio, password, photo,confirmToken, { userRepository,documentRepository }) => { - password = await bcrypt.hash(password,10) - if(!password) { - throwStatusCode('500','Internal server error') - } +module.exports = async (pseudo,alias, bio, photo,confirmToken, { userRepository,documentRepository, accessTokenManager }) => { const userTest = await userRepository.getByEmailOrPseudo(pseudo,pseudo) if(userTest){ throwStatusCode(403,'Pseudo déjà existant') @@ -21,7 +15,6 @@ module.exports = async (pseudo,alias, bio, password, photo,confirmToken, { userR user.alias = alias user.pseudo = pseudo user.bio = bio - user.password = password user.confirm_token = null user.confirmed = true if(user.photo_temporaire && photo){ @@ -29,8 +22,11 @@ module.exports = async (pseudo,alias, bio, password, photo,confirmToken, { userR user.photo_temporaire = photo user.photo = photo } - return userRepository.updateUser(user) - + await userRepository.updateUser(user) + return { + email: user.email, + token: accessTokenManager.generate(user) + } }; diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index 102d3ea..1e89696 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -3,28 +3,21 @@ const User = require('../../../domain/model/User'); const throwStatusCode = require("../utils/throwStatusCode") const crypto = require("crypto"); -module.exports = async (email,spotify_code, { userRepository,mailRepository,spotifyRepository}) => { - let display_name,confirm_token,image,access_token,refresh_token,error - if(spotify_code){ - ({access_token, refresh_token,error} = await spotifyRepository.getToken(spotify_code)) - if(error){ - throwStatusCode(400,error.message) - } - ({email,display_name,image} = await spotifyRepository.getAccountData(access_token)) - image = image?.at(-1)?.url - } +const bcrypt = require("bcrypt"); +module.exports = async (email,password, { userRepository,mailRepository}) => { const userTest = await userRepository.getByEmailOrPseudo(email,email) if(userTest){ throwStatusCode(403,'Email déjà existant') } - confirm_token = crypto.randomBytes(16).toString('hex') + password = await bcrypt.hash(password,10) + if(!password) { + throwStatusCode('500','Internal server error') + } + const confirm_token = Math.floor(Math.random() * (99999 - 10000) + 10000) const userRaw = { email, - alias: display_name ? display_name : null, - photo_temporaire: image ? image : null, confirmed:false, - token: access_token, - refresh_token, + password, confirm_token } let user = new User( @@ -34,13 +27,13 @@ module.exports = async (email,spotify_code, { userRepository,mailRepository,spot const mailOptions = { from: process.env.MAILER_EMAIL, to: email, - subject: 'confirmToken', - text: confirm_token + subject: 'code de comfirmation de la création de votre compte', + text: `vous avez crée votre compte voici votre mail code de confirmation : ${confirm_token}` }; await mailRepository.send(mailOptions) setTimeout(()=>{ userRepository.removeUserByConfirmToken(confirm_token) - },3600*1000*24) + },60*5*1000) return user }; diff --git a/lib/application/use_cases/user/sendResetEmail.js b/lib/application/use_cases/user/sendResetEmail.js index 7f2cd15..09166ce 100644 --- a/lib/application/use_cases/user/sendResetEmail.js +++ b/lib/application/use_cases/user/sendResetEmail.js @@ -3,12 +3,12 @@ const crypto = require("crypto"); module.exports = async (email,{userRepository ,mailRepository}) => { const user = await userRepository.getByEmailOrPseudo(email,email) if(!user || !user.confirmed) return false - const reset_token = crypto.randomBytes(16).toString('hex') + const reset_token = Math.floor(Math.random() * (99999 - 10000) + 10000) user.reset_token = reset_token await userRepository.updateUser(user) const mailOptions = { from: process.env.MAILER_EMAIL, - to: email, + to: "réinitialisation de votre compte", subject: 'reinitialisation de votre mot de passe', text: reset_token }; @@ -17,6 +17,6 @@ module.exports = async (email,{userRepository ,mailRepository}) => { setTimeout(()=>{ delete user.reset_token userRepository.updateUser(user) - },3600*1000) + },60*5*1000) return true }; diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index 8783418..ee9151f 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -13,7 +13,6 @@ const userSignUp = Joi.object().keys({ return value; }, 'pseudo validation').error(validationErrror("pseudo","le pseudo doit être compris entre 3 et 15 caractère")), alias: Joi.string().min(3).max(15).error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), - password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")), photo: Joi.string().max(500), confirmToken: Joi.string().max(50), bio:Joi.string().min(0).max(1500).error(validationErrror("bio","la bio doit faire moins de 1500 caractères")), @@ -22,8 +21,12 @@ const uploadPreview = Joi.object().keys({ file: Joi.any() }) const createUser = Joi.object().keys({ - email: Joi.string().email().min(10).max(40), - spotify_code: Joi.string().max(1000), + email: Joi.string().email().min(10).max(40).required(), + password: Joi.string().max(30).required(), +}) +const authWithSpotify = Joi.object().keys({ + spotify_code: Joi.string().max(1000).required(), + callback: Joi.string().max(100).required() }) const isUser = Joi.object().keys({ pseudo: Joi.string().min(3).max(15).required(), @@ -54,5 +57,6 @@ module.exports = { getUserByConfirmToken, sendResetEmail, resetPassword, - follow + follow, + authWithSpotify } \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index adc930c..2cf3f3f 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -13,7 +13,7 @@ function buildBeans() { return { accessTokenManager: new JwtAccessTokenManager(), userRepository: new UserRepository(), - spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET,process.env.SPOTIFY_AUTH_REDIRECT), + spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET), documentRepository: new documentRepository(), mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), followRepository: new FollowRepository() diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index ec5c867..b118112 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -4,11 +4,10 @@ const axios = require("axios"); const spotifyRepositoryAbstract = require('./interfaces/SpotifyRepositoryAbstract') module.exports = class extends spotifyRepositoryAbstract{ - constructor(client_id,client_secret,redirect_URI) { + constructor(client_id,client_secret) { super(); this.client_id = client_id this.client_secret = client_secret - this.redirect_uri = redirect_URI } @@ -112,10 +111,10 @@ module.exports = class extends spotifyRepositoryAbstract{ return response.data }) } - getToken(code) { + getToken(code,redirectURI) { const payload = new URLSearchParams() payload.append('grant_type','authorization_code') - payload.append('redirect_uri',this.redirect_uri) + payload.append('redirect_uri',redirectURI) payload.append('code',code) return axios.post('https://accounts.spotify.com/api/token',payload, { diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index 8320733..a847839 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -132,6 +132,13 @@ module.exports = class extends userRepository { } }) } + destroy(user) { + this.model.destroy({ + where: { + id_utilisateur: user.id_utilisateur + } + }) + } async getByConfirmToken(token) { const seqUsers = await this.model.findOne({ where: { diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 595763d..9b29c40 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -16,7 +16,7 @@ module.exports = class { refreshToken(token) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - getToken(code) { + getToken(code,redirectURI) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } getAccountData(accessToken){ diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index 98c3d1b..84fd244 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -46,4 +46,8 @@ module.exports = class { getByResetToken(resetToken) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + + destroy(user) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/security/JwtAccessTokenManager.js b/lib/infrastructure/security/JwtAccessTokenManager.js index 6ae9ff9..96b4fac 100644 --- a/lib/infrastructure/security/JwtAccessTokenManager.js +++ b/lib/infrastructure/security/JwtAccessTokenManager.js @@ -7,8 +7,14 @@ const JWT_SECRET_KEY = process.env.SECRET_ENCODER; module.exports = class extends AccessTokenManager { - generate(payload) { - return jwt.sign(payload, JWT_SECRET_KEY); + generate(user) { + return jwt.sign({ + sub: 'my-sub', // needs to match definition above + value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user + aud: 'urn:audience:test', // needs to match definition above + iss: 'urn:issuer:test', // needs to match definition above, + expiresIn: '365d' + }, JWT_SECRET_KEY); } decode(accessToken) { diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index f4b5262..d1b43dd 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -66,7 +66,7 @@ const createServer = async () => { { plugin: HapiCors, options: { - origins: [process.env.FRONT_URL], // Specify the allowed origins + origins: ["http://"+process.env.FRONT_URL], // Specify the allowed origins methods: ['GET', 'POST', 'PUT', 'DELETE'], // Specify the allowed HTTP methods }, } diff --git a/lib/interfaces/controllers/SpotifyController.js b/lib/interfaces/controllers/SpotifyController.js index c1633e5..dcbebff 100644 --- a/lib/interfaces/controllers/SpotifyController.js +++ b/lib/interfaces/controllers/SpotifyController.js @@ -1,6 +1,5 @@ const search = require("../../application/use_cases/spotify/Search"); const getAlbum = require("../../application/use_cases/spotify/getAlbum"); -const getAuthURL = require("../../application/use_cases/spotify/GetAuthURL"); const fetchArtist = require("../../application/use_cases/spotify/FetchArtist"); const getTrack = require("../../application/use_cases/spotify/getTrack"); const getSearchFilters = require("../../application/use_cases/spotify/getSearchFilters"); @@ -78,13 +77,4 @@ module.exports = { return handleError(error) } }, - - async getAuthURL(request,handler) { - try{ - const authURL = await getAuthURL() - return handler.response(authURL).code(200) - }catch(error){ - return handleError(error) - } - } } \ No newline at end of file diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index 6e7f34c..15ae433 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -1,6 +1,7 @@ 'use strict'; const CreateUser = require('../../application/use_cases/user/CreateUser'); +const AuthWithSpotify = require('../../application/use_cases/user/AuthWithSpotify'); const CompleteAccount = require('../../application/use_cases/user/CompleteAccount'); const GetAccessToken = require('../../application/use_cases/security/GetAccessToken'); const handleError = require("./utils/handleError") @@ -20,8 +21,8 @@ module.exports = { const serviceLocator = request.server.app.serviceLocator; // Input - let { pseudo, photo, alias, bio, password,confirmToken } = request.payload - const user = await CompleteAccount(pseudo, alias, bio, password,photo,confirmToken, serviceLocator); + let { pseudo, photo, alias, bio,confirmToken } = request.payload + const user = await CompleteAccount(pseudo, alias, bio,photo,confirmToken, serviceLocator); return handler.response('Votre compte a bien été validé').code(200) }catch(error){ return handleError(error) @@ -57,14 +58,28 @@ module.exports = { return handleError(e) } }, - async createUser(request, handler){ + async authWithSpotify(request, handler){ const serviceLocator = request.server.app.serviceLocator; - const {email,spotify_code} = request.payload + const {spotify_code,callback} = request.payload try{ - const user = await CreateUser(email,spotify_code,serviceLocator); - if(spotify_code){ - refreshToken(user.id_utilisateur,false,serviceLocator) + const returnValue = await AuthWithSpotify(spotify_code,callback,serviceLocator); + if(returnValue.idUtilisateur){ + refreshToken(returnValue.idUtilisateur,false,serviceLocator) + delete returnValue.idUtilisateur } + return handler.response(returnValue).code(200) + + } + catch (e){ + return handleError(e) + } + }, + async createUser(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const {email, password} = request.payload + try{ + const user = await CreateUser(email,password,serviceLocator); + return handler.response("compte créé").code(200) } catch (e){ diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index 3669e62..0907f79 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -159,30 +159,7 @@ module.exports = { } }, }, - - { - method: 'GET', - path: '/spotify/getAuthURL', - handler: spotify.getAuthURL, - options: { - description: 'get auth URL', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description : 'Success'}, - 204: {description : 'No content'}, - 401: {description : 'Unauthorized'}, - 403: {description : 'forbidden'}, - 404: {description : 'Ressource not found'}, - 500: {description : 'Internal server error'}, - 502: {description : 'bad gateway'}, - 503: {description : 'Service unavailable'}, - } - } - }, - }, - } + ]); } }; \ No newline at end of file diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 73ae812..8765f7f 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -8,7 +8,8 @@ const { getUserByConfirmToken, sendResetEmail, resetPassword, - follow + follow, + authWithSpotify } = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); const MAX_BYTE_SIZE =20971520 @@ -130,6 +131,27 @@ module.exports = { } }, }, + { + method: 'POST', + path: '/users/authWithSpotify', + handler: UsersController.authWithSpotify, + options: { + description: 'send Verification email to create account', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + } + } + }, + validate: { + payload: authWithSpotify + } + }, + }, { method: 'GET', path: '/users/isUser', diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 5a21fc1..ff38a06 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -35,7 +35,8 @@ describe('user route', () => { spotifyRepository: mockSpotifyRepository, mailRepository: mockMailRepository, documentRepository: mockDocumentRepository, - followRepository: mockFollowRepository + followRepository: mockFollowRepository, + } server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); @@ -60,13 +61,6 @@ describe('user route', () => { mockSpotifyRepository.getToken= jest.fn(()=> { return {access_token: 1, refresh_token: 1} }) - mockSpotifyRepository.getAccountData = jest.fn(()=> { - return { - email: "testemail@gmail.com", - display_name: 'display_name', - image: [{url:"testurl"}] - } - }) it('should respond code 200 with email inscription', async () => { const res = await server.inject({ @@ -74,40 +68,12 @@ describe('user route', () => { url: '/users/createUser', payload: { email: "tesddesqt@gmaiL.com", + password: "somepassword" } }); - expect(res.statusCode).toBe(200); - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) - }); - it('should respond code 200 with spotify inscription', async () => { - const res = await server.inject({ - method: 'POST', - url: '/users/createUser', - payload: { - spotify_code: "code", - } - }); - expect(res.statusCode).toBe(200); - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(1) - }); - it('should respond code 400 with invalid spotify_code', async () => { - mockSpotifyRepository.getToken= jest.fn(()=> { - return {error: {}} - }) - const res = await server.inject({ - method: 'POST', - url: '/users/createUser', - payload: { - spotify_code: "code", - } - }); - expect(res.statusCode).toBe(400); - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); + it('should respond code 403 with already existing email', async () => { mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> 'something') const res = await server.inject({ @@ -115,11 +81,10 @@ describe('user route', () => { url: '/users/createUser', payload: { email: "tesddesqt@gmaiL.com", + password: "somepassword" } }); expect(res.statusCode).toBe(403); - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); }) @@ -144,7 +109,6 @@ describe('user route', () => { const payload = { pseudo: "testPseudo", alias: "testAlias", - password: "TestPassword", confirmToken: "token", photo:"path", bio:"bio" @@ -162,7 +126,6 @@ describe('user route', () => { const payload = { pseudo: "testPseudo", alias: "testAlias", - password: "TestPassword", confirmToken: "token", bio:"bio" } @@ -180,7 +143,6 @@ describe('user route', () => { const payload = { pseudo: "testPseudo", alias: "testAlias", - password: "TestPassword", confirmToken: "token", bio:"bio" } @@ -196,7 +158,6 @@ describe('user route', () => { const payload = { pseudo: "testPseudo", alias: "testAlias", - password: "TestPassword", confirmToken: "token", bio:"bio" } @@ -500,4 +461,108 @@ describe('user route', () => { expect(res.statusCode).toBe(415); }) }) + describe('AuthWithSpotifyTest', () =>{ + const mockSpotifyCode = 'code' + const email = "some@mail" + const display_name = "name" + const access_token = 'access_token' + const refresh_token = 'refresh_token' + const images = ["https://i.ytimg.com/vi/uLHdmBf1lvs/hq720.jpg?sqp=-oaymwEXCNAFEJQDSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAmH-kUIb43CviOetK-ZjGl0AnSog"] + beforeEach(() => { + mockUserRepository.updateUser = jest.fn(() => "ok") + }) + it("should throw error 400", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return {error: 'some error'} + }) + const res = await server.inject({ + method: 'POST', + url: '/users/authWithSpotify', + payload: { + spotify_code: "eztgergrehre", + }, + }) + expect(res.statusCode).toBe(400) + + }) + it("should throw error 403 1", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: false + } + }) + const res = await server.inject({ + method: 'POST', + url: '/users/authWithSpotify', + payload: { + spotify_code: "eztgergrehre", + callback: "callback" + }, + }) + expect(res.statusCode).toBe(403) + }) + it("should throw error 403 2", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: true, + } + }) + mockAccesTokenManager.generate = jest.fn(() => 'expected_token') + const res = await server.inject({ + method: 'POST', + url: '/users/authWithSpotify', + payload: { + spotify_code: "eztgergrehre", + callback: "callback" + }, + }) + expect(res.statusCode).toBe(403) + }) + it("should return auth token", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: true, + refresh_token: 'someting' + } + }) + mockAccesTokenManager.generate = jest.fn(() => 'expected_token') + const res = await server.inject({ + method: 'POST', + url: '/users/authWithSpotify', + payload: { + spotify_code: "eztgergrehre", + callback: "callback" + }, + }) + expect(res.statusCode).toBe(200) + + }) + }) }); diff --git a/test/unit/application/usecase/user/authWithSpotify.test.js b/test/unit/application/usecase/user/authWithSpotify.test.js new file mode 100644 index 0000000..35c1f3d --- /dev/null +++ b/test/unit/application/usecase/user/authWithSpotify.test.js @@ -0,0 +1,138 @@ +const AuthWithSpotify = require('../../../../../lib/application/use_cases/user/AuthWithSpotify') +const catchError = require("../utils/catchError") +const mockUserRepository = {} +const mockSpotifyRepository = {} +const mockAccessTokenManager = {} +const serviceLocator = { + userRepository: mockUserRepository, + spotifyRepository: mockSpotifyRepository, + accessTokenManager: mockAccessTokenManager +} +describe('AuthWithSpotifyTest', () =>{ + const mockSpotifyCode = 'code' + const email = "some@mail" + const display_name = "name" + const access_token = 'access_token' + const refresh_token = 'refresh_token' + const images = ["https://i.ytimg.com/vi/uLHdmBf1lvs/hq720.jpg?sqp=-oaymwEXCNAFEJQDSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAmH-kUIb43CviOetK-ZjGl0AnSog"] + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(() => { + mockUserRepository.updateUser = jest.fn(() => "ok") + }) + it("should throw error 400", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return {error: 'some error'} + }) + const error = await catchError(async () =>{ + await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + }) + expect(error.code).toBe(400) + + }) + it("should throw error 403 1", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: false + } + }) + const error = await catchError(async () =>{ + await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should throw error 403 2", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: true, + } + }) + mockAccessTokenManager.generate = jest.fn(() => 'expected_token') + const error = await catchError(async () =>{ + await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should return auth token", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: true, + refresh_token: 'something' + } + }) + mockAccessTokenManager.generate = jest.fn(() => 'expected_token') + const result = await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + expect(result.email).toBe(email) + expect(result.token).toBe("expected_token") + expect(mockAccessTokenManager.generate).toHaveBeenCalled() + }) + it("should return confirm token 1", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return null + }) + mockUserRepository.persist = jest.fn(() => 'ok') + const result = await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + expect(result.confirmToken).not.toBeNull() + expect(mockUserRepository.persist).toHaveBeenCalled() + }) + it("should return confirm token 2", async ()=>{ + mockSpotifyRepository.getToken = jest.fn(()=> { + return { + access_token, + refresh_token + } + }) + mockSpotifyRepository.getAccountData = jest.fn(()=> { + return {email,display_name,images} + }) + mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { + return { + confirmed: false, + confirm_token: "expected_token", + refresh_token: "something" + } + }) + mockUserRepository.persist = jest.fn(() => 'ok') + const result = await AuthWithSpotify(mockSpotifyCode, 'callback',serviceLocator) + expect(result.confirmToken).toBe("expected_token") + expect(mockUserRepository.persist).not.toHaveBeenCalled() + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/user/completeAccount.test.js b/test/unit/application/usecase/user/completeAccount.test.js index 6e57de9..ea5bcff 100644 --- a/test/unit/application/usecase/user/completeAccount.test.js +++ b/test/unit/application/usecase/user/completeAccount.test.js @@ -2,24 +2,27 @@ const completeAccount = require('../../../../../lib/application/use_cases/user/C const catchError = require("../utils/catchError") const mockUserRepository = {} const mockDocumentRepository = {} +const mockAccessTokenManager = {} const serviceLocator = { userRepository: mockUserRepository, documentRepository: mockDocumentRepository, + accessTokenManager: mockAccessTokenManager } -describe('createUser', () =>{ +describe('CompleteAccount', () =>{ const pseudo= 'testPseudo' const alias= 'testAlias' const bio= 'testBio' - const password= 'testPassword' const confirm_token= 'confirmToken' const photo = "otherpath/to/file" const email = "testemail@gmail.com" const photo_temporaire = "path/to/file" const confirmed = false const id_utilisateur = 1 + const mockToken = 'token' const mockUser = { id_utilisateur,email,photo_temporaire,confirmed,confirm_token } + mockAccessTokenManager.generate = jest.fn(() => mockToken) mockDocumentRepository.deleteFile = jest.fn(()=>{}) mockUserRepository.updateUser = jest.fn((user)=>user) afterEach(()=>{ @@ -30,15 +33,10 @@ describe('createUser', () =>{ mockUserRepository.getByConfirmToken = jest.fn(()=>{ return {...mockUser} }) - const user = await completeAccount(pseudo,alias,bio,password,photo,confirm_token,serviceLocator) - expect(user.id_utilisateur).toBe(1) - expect(user.pseudo).toBe(pseudo) + const user = await completeAccount(pseudo,alias,bio,photo,confirm_token,serviceLocator) + expect(user.token).toBe(mockToken) expect(user.email).toBe(email) - expect(user.alias).toBe(alias) - expect(user.photo).toBe(photo) - expect(user.photo_temporaire).toBe(photo) - expect(user.confirm_token).toBe(null) - expect(user.confirmed).toBe(true) + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1) }) it("should return user without replaced photo ", async ()=>{ @@ -46,16 +44,10 @@ describe('createUser', () =>{ mockUserRepository.getByConfirmToken = jest.fn(()=>{ return {...mockUser} }) - const user = await completeAccount(pseudo,alias,bio,password,null,confirm_token,serviceLocator) - console.log(user) - - expect(user.id_utilisateur).toBe(1) - expect(user.pseudo).toBe(pseudo) + const user = await completeAccount(pseudo,alias,bio,null,confirm_token,serviceLocator) + expect(user.token).toBe(mockToken) expect(user.email).toBe(email) - expect(user.alias).toBe(alias) - expect(user.photo_temporaire).toBe(photo_temporaire) - expect(user.confirm_token).toBe(null) - expect(user.confirmed).toBe(true) + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0) }) it("should return user without alias", async ()=>{ @@ -63,14 +55,10 @@ describe('createUser', () =>{ mockUserRepository.getByConfirmToken = jest.fn(()=>{ return {...mockUser} }) - const user = await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) - expect(user.id_utilisateur).toBe(1) - expect(user.pseudo).toBe(pseudo) + const user = await completeAccount(pseudo,null,bio,null,confirm_token,serviceLocator) + expect(user.token).toBe(mockToken) expect(user.email).toBe(email) - expect(user.alias).toBe(pseudo) - expect(user.photo_temporaire).toBe(photo_temporaire) - expect(user.confirm_token).toBe(null) - expect(user.confirmed).toBe(true) + }) it("should throw error 403 user already exists", async ()=>{ mockUserRepository.getByConfirmToken = jest.fn(()=>{ @@ -78,7 +66,7 @@ describe('createUser', () =>{ }) mockUserRepository.getByEmailOrPseudo = jest.fn(()=>'something') const error = await catchError(async ()=>{ - await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) + await completeAccount(pseudo,null,bio,null,confirm_token,serviceLocator) }) expect(error.code).toBe(403) }) @@ -86,7 +74,7 @@ describe('createUser', () =>{ mockUserRepository.getByConfirmToken = jest.fn(()=>null) mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) const error = await catchError(async ()=>{ - await completeAccount(pseudo,null,bio,password,null,confirm_token,serviceLocator) + await completeAccount(pseudo,null,bio,null,confirm_token,serviceLocator) }) expect(error.code).toBe(400) }) diff --git a/test/unit/application/usecase/user/createUser.test.js b/test/unit/application/usecase/user/createUser.test.js index c55f527..a7c0ff9 100644 --- a/test/unit/application/usecase/user/createUser.test.js +++ b/test/unit/application/usecase/user/createUser.test.js @@ -1,7 +1,6 @@ const createUser = require('../../../../../lib/application/use_cases/user/CreateUser') const catchError = require("../utils/catchError") const mockUserRepository = {} -const mockSpotifyRepository = {} const mockMailRepository = {} const email = "testemail@gmail.com" const display_name = "display_name" @@ -16,59 +15,23 @@ describe("createUser", ()=>{ return user }) mockMailRepository.send = jest.fn(option => null) - mockSpotifyRepository.getToken= jest.fn(()=> { - return {access_token: 1, refresh_token: 1} - }) - mockSpotifyRepository.getAccountData = jest.fn(()=> { - return { - email, - display_name, - image - } - }) + const serviceLocator = { userRepository: mockUserRepository, - spotifyRepository: mockSpotifyRepository, mailRepository: mockMailRepository } it('should return user with email inscription', async () => { - const user = await createUser(email,null,serviceLocator) - expect(user.email).toBe(email) - expect(user.confirmed).toBe(false) - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) - }); - it('should return user with spotify inscription', async () => { - const user = await createUser(null,"code",serviceLocator) + const user = await createUser(email,"password",serviceLocator) expect(user.email).toBe(email) - expect(user.alias).toBe(display_name) - expect(user.photo_temporaire).toBe('testurl') - expect(user.token).toBe(1) - expect(user.refresh_token).toBe(1) expect(user.confirmed).toBe(false) - - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(1) - }); - it('should throw error 400 invalid code', async () => { - mockSpotifyRepository.getToken= jest.fn(()=> { - return {error: {}} - }) - const error = await catchError(async ()=>{ - await createUser(null,"code",serviceLocator) - }) - expect(error.code).toBe(400) - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) + !expect(user.password).not.toBe(null) }); it('should throw error 403 email already exists', async () => { mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> 'something') const error = await catchError(async ()=>{ - await createUser(email,null,serviceLocator) + await createUser(email,"password",serviceLocator) }) expect(error.code).toBe(403); - expect(mockSpotifyRepository.getToken).toHaveBeenCalledTimes(0) - expect(mockSpotifyRepository.getAccountData).toHaveBeenCalledTimes(0) }); }) \ No newline at end of file From dc36825aedc8080cb0b20e6d58e171d29cb104b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Tue, 13 Feb 2024 10:56:17 +0100 Subject: [PATCH 31/67] auth with spotify --- lib/application/use_cases/user/AuthWithSpotify.js | 11 +++-------- lib/domain/model/User.js | 1 + .../orm/sequelize/models/Utilisateur.js | 6 +++++- lib/infrastructure/orm/sequelize/sequelize.js | 2 +- .../application/usecase/user/authWithSpotify.test.js | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/application/use_cases/user/AuthWithSpotify.js b/lib/application/use_cases/user/AuthWithSpotify.js index e719062..ee3c53e 100644 --- a/lib/application/use_cases/user/AuthWithSpotify.js +++ b/lib/application/use_cases/user/AuthWithSpotify.js @@ -9,7 +9,6 @@ module.exports = async (spotify_code, callback, { userRepository,spotifyReposito if(error){ throwStatusCode(400,error.message) } - console.log("peut etres") const {email,display_name,images} = await spotifyRepository.getAccountData(access_token) const image = images?.at(-1)?.url const userTest = await userRepository.getByEmailOrPseudo(email,email) @@ -23,10 +22,6 @@ module.exports = async (spotify_code, callback, { userRepository,spotifyReposito } } if(!userTest){ - // const password = await bcrypt.hash(crypto.randomBytes(16).toString('hex'),10) - // if(!password) { - // throwStatusCode('500','Internal server error') - // } const confirm_token = crypto.randomBytes(16).toString('hex') const userRaw = { email, @@ -34,7 +29,7 @@ module.exports = async (spotify_code, callback, { userRepository,spotifyReposito photo: image ? image : null, confirmed:false, token: access_token, - // password, + auth_with_spotify: true, refresh_token, confirm_token } @@ -48,8 +43,8 @@ module.exports = async (spotify_code, callback, { userRepository,spotifyReposito confirmToken: confirm_token, } } - if(!userTest.refresh_token || !userTest.confirm_token){ - throwStatusCode(403, 'erreur') + if(!userTest.auth_with_spotify){ + throwStatusCode(403, 'un compte existe déjà avec ce mail') } return { confirmToken: userTest.confirm_token diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index 61e7ae6..3d6e3d9 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -17,6 +17,7 @@ module.exports = class { this.ban_until = userRaw?.ban_until this.confirmed= userRaw?.confirmed this.confirm_token = userRaw?.confirm_token + this.auth_with_spotify = userRaw?.auth_with_spotify this.type = 'user' } diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index 3738d11..e40e811 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -33,6 +33,10 @@ module.exports = (sequelize) => { type: DataTypes.STRING(400), unique: true, }, + auth_with_spotify: { + type: DataTypes.BOOLEAN, + defaultValue: false + }, id_spotify: { type: DataTypes.STRING(50), }, @@ -54,7 +58,7 @@ module.exports = (sequelize) => { confirm_token: { type: DataTypes.STRING(50), }, - reset_token: { + reset_token: { type: DataTypes.STRING(50), }, }, diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index f828b62..77ce192 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -61,7 +61,7 @@ ArtisteModel.belongsToMany(UserModel, ) UserModel.belongsToMany(ReviewModel,{ as: 'user_like_review', - foreignKey: 'id_artiste', + foreignKey: 'id_user', through : 'like_review' }) ReviewModel.belongsToMany(UserModel,{ diff --git a/test/unit/application/usecase/user/authWithSpotify.test.js b/test/unit/application/usecase/user/authWithSpotify.test.js index 35c1f3d..ba6e673 100644 --- a/test/unit/application/usecase/user/authWithSpotify.test.js +++ b/test/unit/application/usecase/user/authWithSpotify.test.js @@ -124,7 +124,7 @@ describe('AuthWithSpotifyTest', () =>{ }) mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { return { - confirmed: false, + auth_with_spotify: true, confirm_token: "expected_token", refresh_token: "something" } From 948237f2bf6d52c2a94a01c0f5ac7c656ca80299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:21:03 +0100 Subject: [PATCH 32/67] fix db (#35) --- lib/infrastructure/orm/sequelize/sequelize.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index 77ce192..ce3028f 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -32,7 +32,7 @@ UserModel.belongsToMany(UserModel, UserModel.belongsToMany(CommentaireModel, { as: 'user_like_comment', - foreignKey: 'id_user', + foreignKey: 'id_utilisateur', through : 'like_commentaire' } ) @@ -61,7 +61,7 @@ ArtisteModel.belongsToMany(UserModel, ) UserModel.belongsToMany(ReviewModel,{ as: 'user_like_review', - foreignKey: 'id_user', + foreignKey: 'id_utilisateur', through : 'like_review' }) ReviewModel.belongsToMany(UserModel,{ @@ -91,6 +91,10 @@ CommentaireModel.belongsTo(ReviewModel,{ foreignKey:'id_review' }) +CommentaireModel.belongsTo(UserModel,{ + as:'user_review', + foreignKey:'id_utilisateur' +}) CommentaireModel.hasMany(CommentaireModel,{ as: 'reponse', foreignKey: 'id_reponse' }) From 0ecd643f903b8d1bcf7bcf240919ff2554767dd8 Mon Sep 17 00:00:00 2001 From: Yousrah Soule <118174920+yousrah24@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:32:02 +0100 Subject: [PATCH 33/67] Gestion relation amis (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ajout de la requete de recuperation des infos d'une chanson ou un album * ajout des test de getAlbum et getTrack * test correction * mise en place des requetes permettant de gerer la relation amis entre utilisateur + structuration de l'envoi de mail * Modification apportee pour l'utilisation d'un token d'indentifiant utilisateur au lieu de son id * ajout des test de getAlbum et getTrack * test correction * mise en place des requetes permettant de gerer la relation amis entre utilisateur + structuration de l'envoi de mail * Modification apportee pour l'utilisation d'un token d'indentifiant utilisateur au lieu de son id * renase correction --------- Co-authored-by: Maël --- .../use_cases/friend/acceptRequestUser.js | 23 + .../use_cases/friend/followUser.js | 94 ++++ .../use_cases/friend/getListFriends.js | 15 + .../use_cases/friend/getListFriendsRequest.js | 15 + .../use_cases/friend/getProfilFriend.js | 22 + .../use_cases/friend/unfollowUser.js | 20 + lib/application/use_cases/user/CreateUser.js | 71 +++- .../use_cases/user/changePrivateStatus.js | 12 + lib/domain/model/Friend.js | 13 + lib/domain/model/User.js | 7 +- lib/infrastructure/config/service-locator.js | 4 +- .../orm/sequelize/models/Amis.js | 4 +- .../orm/sequelize/models/Utilisateur.js | 6 +- lib/infrastructure/orm/sequelize/sequelize.js | 12 +- .../repositories/FriendRepository.js | 99 +++++ .../repositories/UserRepository.js | 19 + .../interfaces/FriendRepositoryAbstract.js | 27 ++ .../interfaces/UserRepositoryAbstract.js | 3 + lib/infrastructure/webserver/server.js | 3 +- .../controllers/FriendsController.js | 95 +++++ lib/interfaces/controllers/UsersController.js | 13 + lib/interfaces/routes/friends.js | 155 +++++++ lib/interfaces/routes/spotify.js | 2 +- lib/interfaces/routes/users.js | 21 +- test/integration/friend.test.js | 400 ++++++++++++++++++ test/integration/users.test.js | 26 +- .../usecase/friend/acceptResquestUser.test.js | 86 ++++ .../usecase/friend/followUser.test.js | 92 ++++ .../usecase/friend/getListFriends.test.js | 102 +++++ .../friend/getListFriendsRequest.test.js | 86 ++++ .../usecase/friend/getProfilFriend.test.js | 108 +++++ .../usecase/friend/unfollowUser.test.js | 79 ++++ 32 files changed, 1716 insertions(+), 18 deletions(-) create mode 100644 lib/application/use_cases/friend/acceptRequestUser.js create mode 100644 lib/application/use_cases/friend/followUser.js create mode 100644 lib/application/use_cases/friend/getListFriends.js create mode 100644 lib/application/use_cases/friend/getListFriendsRequest.js create mode 100644 lib/application/use_cases/friend/getProfilFriend.js create mode 100644 lib/application/use_cases/friend/unfollowUser.js create mode 100644 lib/application/use_cases/user/changePrivateStatus.js create mode 100644 lib/domain/model/Friend.js create mode 100644 lib/infrastructure/repositories/FriendRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js create mode 100644 lib/interfaces/controllers/FriendsController.js create mode 100644 lib/interfaces/routes/friends.js create mode 100644 test/integration/friend.test.js create mode 100644 test/unit/application/usecase/friend/acceptResquestUser.test.js create mode 100644 test/unit/application/usecase/friend/followUser.test.js create mode 100644 test/unit/application/usecase/friend/getListFriends.test.js create mode 100644 test/unit/application/usecase/friend/getListFriendsRequest.test.js create mode 100644 test/unit/application/usecase/friend/getProfilFriend.test.js create mode 100644 test/unit/application/usecase/friend/unfollowUser.test.js diff --git a/lib/application/use_cases/friend/acceptRequestUser.js b/lib/application/use_cases/friend/acceptRequestUser.js new file mode 100644 index 0000000..31ba4e2 --- /dev/null +++ b/lib/application/use_cases/friend/acceptRequestUser.js @@ -0,0 +1,23 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, amiIdUtilisateur, {accessTokenManager, userRepository, friendRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + const ami = await userRepository.getByUser(amiIdUtilisateur) + if(!ami) { + throwStatusCode(400,'id ami invalide') + } + + const friend = await friendRepository.accept(id, amiIdUtilisateur) + if(!friend) { + throwStatusCode(403,'Il existe aucune relation entre ces deux utilisateurs') + } + + return friend +} + diff --git a/lib/application/use_cases/friend/followUser.js b/lib/application/use_cases/friend/followUser.js new file mode 100644 index 0000000..dba40cd --- /dev/null +++ b/lib/application/use_cases/friend/followUser.js @@ -0,0 +1,94 @@ +'use strict'; + +const Friend = require('../../../domain/model/Friend'); +const throwStatusCode = require("../utils/throwStatusCode"); + +module.exports = async (token, amiIdUtilisateur, {accessTokenManager, friendRepository, userRepository, mailRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + const ami = await userRepository.getByUser(amiIdUtilisateur); + if (!ami) { + throwStatusCode(400, 'id ami invalide'); + } + + const userRaw = { + id_utilisateur: id, + amiIdUtilisateur: amiIdUtilisateur, + en_attente: ami.is_private, + createdAt: undefined, + updatedAt: undefined + }; + + let friend = new Friend(userRaw); + friend = await friendRepository.persist(friend); + if (!friend) { + throwStatusCode(403, 'Vous avez déjà ajouté cet utilisateur.') + } + if (ami.is_private) { + const mailOptions = { + from: process.env.MAILER_EMAIL, + to: ami.email, + subject: 'Nouvelle demande d\'ami sur Solimbo', + html: ` + + + + + + Nouvelle demande d'ami sur Solimbo + + + +

+ + + +

Nouvelle demande d'ami sur Solimbo

+
+

Bonjour,

+

Vous avez reçu une nouvelle demande d'ami sur Solimbo de la part de ${user.pseudo}.

+

Connectez-vous à Solimbo pour accepter ou rejeter la demande.

+
+ Accepter la demande +
+ + + ` + } + await mailRepository.send(mailOptions); + } + return friend; +}; diff --git a/lib/application/use_cases/friend/getListFriends.js b/lib/application/use_cases/friend/getListFriends.js new file mode 100644 index 0000000..6838eef --- /dev/null +++ b/lib/application/use_cases/friend/getListFriends.js @@ -0,0 +1,15 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, {accessTokenManager, userRepository, friendRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + + const friends = await friendRepository.getListFriendsById(id) + + return friends +} \ No newline at end of file diff --git a/lib/application/use_cases/friend/getListFriendsRequest.js b/lib/application/use_cases/friend/getListFriendsRequest.js new file mode 100644 index 0000000..b1ad38c --- /dev/null +++ b/lib/application/use_cases/friend/getListFriendsRequest.js @@ -0,0 +1,15 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, {accessTokenManager, userRepository, friendRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + + const requests = await friendRepository.getRequestFriendsById(id) + + return requests +} \ No newline at end of file diff --git a/lib/application/use_cases/friend/getProfilFriend.js b/lib/application/use_cases/friend/getProfilFriend.js new file mode 100644 index 0000000..7af357c --- /dev/null +++ b/lib/application/use_cases/friend/getProfilFriend.js @@ -0,0 +1,22 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, amiIdUtilisateur, {accessTokenManager, userRepository, friendRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + const ami = await userRepository.getByUser(amiIdUtilisateur) + if(!ami) { + throwStatusCode(400,'id ami invalide') + } + + const profil = await friendRepository.getById(id, amiIdUtilisateur) + if(!profil) { + throwStatusCode(403,'Il existe aucune relation entre ces deux utilisateurs') + } + + return ami +} \ No newline at end of file diff --git a/lib/application/use_cases/friend/unfollowUser.js b/lib/application/use_cases/friend/unfollowUser.js new file mode 100644 index 0000000..6ac7e66 --- /dev/null +++ b/lib/application/use_cases/friend/unfollowUser.js @@ -0,0 +1,20 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, amiIdUtilisateur, {accessTokenManager, userRepository, friendRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + const ami = await userRepository.getByUser(amiIdUtilisateur) + if(!ami) { + throwStatusCode(400,'id ami invalide') + } + const friend = await friendRepository.removeFriendById(id, amiIdUtilisateur) + if(!friend) { + throwStatusCode(403,'Il existe aucune relation entre ces deux utilisateurs') + } + return friend +} \ No newline at end of file diff --git a/lib/application/use_cases/user/CreateUser.js b/lib/application/use_cases/user/CreateUser.js index 1e89696..17bfd67 100644 --- a/lib/application/use_cases/user/CreateUser.js +++ b/lib/application/use_cases/user/CreateUser.js @@ -23,13 +23,80 @@ module.exports = async (email,password, { userRepository,mailRepository}) => { let user = new User( userRaw); user = await userRepository.persist(user) + if (!user) { + throwStatusCode(400, 'Ce compte n\'a pas pu être créé. Veuillez réessayer plus tard.') + } const mailOptions = { from: process.env.MAILER_EMAIL, to: email, - subject: 'code de comfirmation de la création de votre compte', - text: `vous avez crée votre compte voici votre mail code de confirmation : ${confirm_token}` + subject: 'Votre token de confirmation pour votre connexion', + html: ` + + + + + + Votre token de confirmation + + + +
+ + + +

Votre token de confirmation


+

Voici votre token de confirmation pour votre prochaine connexion sur Solimbo :

+
+

${confirm_token}

+
+

Utilisez ce token pour compléter votre inscription sur notre site.

+ Se connecter +
+ + + ` }; + await mailRepository.send(mailOptions) setTimeout(()=>{ diff --git a/lib/application/use_cases/user/changePrivateStatus.js b/lib/application/use_cases/user/changePrivateStatus.js new file mode 100644 index 0000000..fd262ec --- /dev/null +++ b/lib/application/use_cases/user/changePrivateStatus.js @@ -0,0 +1,12 @@ +'use strict'; + +const throwStatusCode = require("../utils/throwStatusCode") + +module.exports = async (token, {accessTokenManager, userRepository}) => { + const id = accessTokenManager.decode(token)?.value + const user = await userRepository.changePrivateStatus(id) + if (!user) { + throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); + } + return user +} \ No newline at end of file diff --git a/lib/domain/model/Friend.js b/lib/domain/model/Friend.js new file mode 100644 index 0000000..d430343 --- /dev/null +++ b/lib/domain/model/Friend.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = class { + + constructor(userRaw) { + this.id_utilisateur = userRaw?.id_utilisateur + this.amiIdUtilisateur = userRaw?.amiIdUtilisateur + this.en_attente = userRaw?.en_attente + this.createdAt = userRaw?.createdAt + this.updatedAt = userRaw?.updatedAt + this.type = 'amis' + } +} \ No newline at end of file diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index 3d6e3d9..2f107c5 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -3,9 +3,9 @@ module.exports = class { constructor(userRaw) { - this.id_utilisateur = userRaw?.id_utilisateur; - this.pseudo = userRaw?.pseudo; - this.email = userRaw?.email; + this.id_utilisateur = userRaw?.id_utilisateur + this.pseudo = userRaw?.pseudo + this.email = userRaw?.email this.alias = userRaw?.alias this.photo = userRaw?.photo this.photo_temporaire = userRaw?.photo_temporaire @@ -18,6 +18,7 @@ module.exports = class { this.confirmed= userRaw?.confirmed this.confirm_token = userRaw?.confirm_token this.auth_with_spotify = userRaw?.auth_with_spotify + this.is_private = userRaw?.is_private this.type = 'user' } diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index 2cf3f3f..f163518 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -8,6 +8,7 @@ const JwtAccessTokenManager = require('../security/JwtAccessTokenManager'); const documentRepository= require('../repositories/DocumentRepository'); const NodemailerRepository= require('../repositories/NodemailerRepository'); const FollowRepository= require('../repositories/FollowRepository'); +const FriendRepository= require('../repositories/FriendRepository'); function buildBeans() { return { @@ -16,7 +17,8 @@ function buildBeans() { spotifyRepository: new spotifyRepository(process.env.CLIENT_ID,process.env.CLIENT_SECRET), documentRepository: new documentRepository(), mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), - followRepository: new FollowRepository() + followRepository: new FollowRepository(), + friendRepository: new FriendRepository(), }; } diff --git a/lib/infrastructure/orm/sequelize/models/Amis.js b/lib/infrastructure/orm/sequelize/models/Amis.js index 6a380e7..584d1b6 100644 --- a/lib/infrastructure/orm/sequelize/models/Amis.js +++ b/lib/infrastructure/orm/sequelize/models/Amis.js @@ -2,13 +2,11 @@ const { DataTypes } = require('sequelize'); module.exports = (sequelize) => { return sequelize.define('amis', { - - // attributes en_attente: { type: DataTypes.BOOLEAN, allowNull: false, }, }, - { freezeTableName: true,}); + { freezeTableName: true,}); }; diff --git a/lib/infrastructure/orm/sequelize/models/Utilisateur.js b/lib/infrastructure/orm/sequelize/models/Utilisateur.js index e40e811..92143ab 100644 --- a/lib/infrastructure/orm/sequelize/models/Utilisateur.js +++ b/lib/infrastructure/orm/sequelize/models/Utilisateur.js @@ -61,8 +61,12 @@ module.exports = (sequelize) => { reset_token: { type: DataTypes.STRING(50), }, + is_private : { + type: DataTypes.BOOLEAN, + defaultValue: false + }, }, - { freezeTableName: true,} + { freezeTableName: true,} ); }; diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index ce3028f..48a3032 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -23,9 +23,15 @@ UserModel.belongsTo(RoleModel, UserModel.belongsToMany(UserModel, { - as: 'amis_2', - foreignKey: 'amis_2_id', - through : AmisModel + as: 'ami', + foreignKey: { + name:'id_utilisateur', + primaryKey: true, + }, + through: { + model: AmisModel, + primaryKey: true, + } } ) diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js new file mode 100644 index 0000000..bb231f8 --- /dev/null +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -0,0 +1,99 @@ +'use strict'; + +const sequelize = require('../orm/sequelize/sequelize'); +const Friend = require('../../domain/model/Friend'); +const User = require('../../domain/model/User'); +const FriendRepositoryAbstract = require('./interfaces/FriendRepositoryAbstract'); +const { Op } = require('sequelize'); + +module.exports = class extends FriendRepositoryAbstract { + + constructor() { + super(); + this.db = sequelize; + this.model = this.db.model('amis'); + this.UserModel = this.db.model('utilisateur'); + } + + async persist(friendEntity) { + friendEntity.createdAt = sequelize.literal('CURRENT_TIMESTAMP'); + friendEntity.updatedAt = sequelize.literal('CURRENT_TIMESTAMP'); + console.log(friendEntity); + const user = await this.model.create(friendEntity); + user.save(); + return this.createAmis(user); + } + + async getById(id_utilisateur, amiIdUtilisateur) { + const user = await this.model.findOne({ + where: { + id_utilisateur : id_utilisateur, + amiIdUtilisateur : amiIdUtilisateur + }, + }); + return this.createAmis(user); + } + + createAmis(seq) { + return seq ? new Friend(seq) : null; + } + + async getListFriendsById(id) { + const idFriends = await this.model.findAll({ + attributes: ['amiIdUtilisateur'], + where: { + id_utilisateur: id, + en_attente: false + }, + }); + + let friends = await this.UserModel.findAll({ + where: { + id_utilisateur: { [Op.in]: idFriends.map((f)=> f.getDataValue("amiIdUtilisateur"))} + } + }) + return friends ? friends.map( f => new User(f)) : null; + } + + async removeFriendById(id_utilisateur, amiIdUtilisateur) { + const user = await this.model.destroy({ + where: { + id_utilisateur: id_utilisateur, + amiIdUtilisateur: amiIdUtilisateur + } + }); + return user; + } + + async getRequestFriendsById(id) { + const idFriends = await this.model.findAll({ + where: { + en_attente: true, + amiIdUtilisateur: id + }, + }); + const friends = await this.UserModel.findAll({ + where: { + id_utilisateur: { [Op.in]: idFriends.map((f)=> f.getDataValue("amiIdUtilisateur"))} + } + }) + return friends ? friends.map( f => new User(f)) : null; + } + + async accept(id, amiIdUtilisateur) { + const user = await this.model.update( + { + en_attente: false, + updatedAt: sequelize.literal('CURRENT_TIMESTAMP') + }, + { + where: { + id_utilisateur: id, + amiIdUtilisateur: amiIdUtilisateur + } + } + ); + return this.createAmis(user); + } + +}; diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index a847839..f8afd99 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -160,5 +160,24 @@ module.exports = class extends userRepository { }); return this.createUser(seqUsers) } + + async changePrivateStatus(id){ + const user = await this.model.findOne({ + where: {id_utilisateur: id} + }); + + if (user) { + const nouveauStatut = user.is_private ? false : true; + + await this.model.update( + { is_private: nouveauStatut }, + { where: { id_utilisateur: id } } + ); + + } + + return this.createUser(user); +} + }; diff --git a/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js new file mode 100644 index 0000000..88ba22d --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js @@ -0,0 +1,27 @@ +'use strict'; + +module.exports = class { + + persist(friendEntity) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getById(id_utilisateur, amiIdUtilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getListFriendsById(id){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + removeFriendById(id_utilisateur, amiIdUtilisateur){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getRequestFriendsById(id){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + accept(id, amiIdUtilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + +}; diff --git a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js index 84fd244..4ff7da4 100644 --- a/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/UserRepositoryAbstract.js @@ -50,4 +50,7 @@ module.exports = class { destroy(user) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + setPrivateStatus(id) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index d1b43dd..26c3a5c 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -83,7 +83,8 @@ const createServer = async () => { await server.register([ require('../../interfaces/routes/users'), require('../../interfaces/routes/spotify'), - require('../../interfaces/routes/upload') + require('../../interfaces/routes/upload'), + require('../../interfaces/routes/friends'), ]); refreshTokens(serviceLocator) removeExpiredConfirmTokens(serviceLocator) diff --git a/lib/interfaces/controllers/FriendsController.js b/lib/interfaces/controllers/FriendsController.js new file mode 100644 index 0000000..88b760d --- /dev/null +++ b/lib/interfaces/controllers/FriendsController.js @@ -0,0 +1,95 @@ +'use strict'; + +const followUser = require('../../application/use_cases/friend/followUser') +const acceptRequestUser = require('../../application/use_cases/friend/acceptRequestUser') +const unfollowUser = require('../../application/use_cases/friend/unfollowUser') +const getListFriends = require('../../application/use_cases/friend/getListFriends') +const getListFriendsRequest = require('../../application/use_cases/friend/getListFriendsRequest') +const getProfilFriend = require('../../application/use_cases/friend/getProfilFriend') +const handleError = require("./utils/handleError") + +module.exports = { + + async followUser(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {amiIdUtilisateur} = request.payload + try{ + await followUser(token, amiIdUtilisateur, serviceLocator); + return handler.response("Demande d'ami envoyée / Ajout d'ami bien effectué").code(200) + } + catch (e){ + return handleError(e) + } + }, + + async unfollowUser(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {amiIdUtilisateur} = request.payload + try{ + await unfollowUser(token, amiIdUtilisateur, serviceLocator); + return handler.response("Suppression d'un ami bien effectuée").code(200) + } + catch (e){ + return handleError(e) + } + }, + + async acceptRequestUser(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {amiIdUtilisateur} = request.payload; + try{ + const user = await acceptRequestUser(token, amiIdUtilisateur, serviceLocator); + return handler.response("Demande d'ami bien acceptée").code(200) + } + catch (e){ + return handleError(e) + } + }, + + async getListFriends(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + try{ + const friends = await getListFriends(token, serviceLocator); + return handler.response(friends).code(200) + } + catch (e){ + return handleError(e) + } + }, + + async getListFriendsRequest(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + try{ + const requests = await getListFriendsRequest(token, serviceLocator); + return handler.response(requests).code(200) + } + catch (e){ + return handleError(e) + } + }, + + async getProfilFriend(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {amiIdUtilisateur} = request.payload + try{ + const user = await getProfilFriend(token, amiIdUtilisateur, serviceLocator); + return handler.response(user).code(200) + } + catch (e){ + return handleError(e) + } + } + +} diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index 15ae433..ea2f6b9 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -10,6 +10,7 @@ const getUserByPseudo = require("../../application/use_cases/user/getUserByPseud const getUserByConfirmToken = require("../../application/use_cases/user/getUserByConfirmToken") const sendResetEmail = require("../../application/use_cases/user/sendResetEmail") const resetPassword = require("../../application/use_cases/user/resetPassword") +const changePrivateStatus = require("../../application/use_cases/user/changePrivateStatus") const refreshToken = require("../../application/use_cases/spotify/RefreshToken") const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); const follow = require("../../application/use_cases/user/follow"); @@ -146,5 +147,17 @@ module.exports = { }catch(error){ return handleError(error) } + }, + async changePrivateStatus(request,handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + try{ + await changePrivateStatus(token,serviceLocator); + return handler.response("Le statut du compte a bien été mis à jour").code(200) + } + catch (e){ + return handleError(e) + } } }; diff --git a/lib/interfaces/routes/friends.js b/lib/interfaces/routes/friends.js new file mode 100644 index 0000000..b54d548 --- /dev/null +++ b/lib/interfaces/routes/friends.js @@ -0,0 +1,155 @@ +'use strict'; + +const FriendsController = require('../controllers/FriendsController') +const Joi = require('joi') + +module.exports = { + name: 'amis', + version: '1.0.0', + register: async (server) => { + + server.route([ + { + method: 'POST', + path: '/amis/follow', + handler: FriendsController.followUser, + options: { + description: 'Follow a user', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + amiIdUtilisateur: Joi.number().required(), + }) + } + }, + }, + { + method: 'POST', + path: '/amis/unfollow', + handler: FriendsController.unfollowUser, + options: { + description: 'Unfollow a user', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + amiIdUtilisateur: Joi.number().required(), + }) + } + }, + }, + { + method: 'POST', + path: '/amis/accept', + handler: FriendsController.acceptRequestUser, + options: { + description: 'Accept a friend request', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + amiIdUtilisateur: Joi.number().required(), + }) + } + }, + }, + { + method: 'GET', + path: '/amis/request', + handler: FriendsController.getListFriendsRequest, + options: { + description: 'Get a user\'s request friends list', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + }, + }, + { + method: 'GET', + path: '/amis/list', + handler: FriendsController.getListFriends, + options: { + description: 'Get a user\'s friends list', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + } + }, + { + method: 'POST', + path: '/amis/profil', + handler: FriendsController.getProfilFriend, + options: { + description: 'Get a friend profil', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 403: {description : 'forbidden'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + } + } + }, + validate: { + payload: Joi.object().keys({ + amiIdUtilisateur: Joi.number().required(), + }) + } + } + }, + + ]); + } +}; \ No newline at end of file diff --git a/lib/interfaces/routes/spotify.js b/lib/interfaces/routes/spotify.js index 0907f79..0070202 100644 --- a/lib/interfaces/routes/spotify.js +++ b/lib/interfaces/routes/spotify.js @@ -11,7 +11,7 @@ module.exports = { path: '/spotify/search', handler: spotify.search, options: { - description: 'get a spotify track', + description: 'get a spotify search', tags: ['api'], plugins: { 'hapi-swagger': { diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 8765f7f..439f321 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -260,7 +260,26 @@ module.exports = { payload: follow } }, - } + }, + { + method: 'POST', + path: '/users/status', + handler: UsersController.changePrivateStatus, + options: { + description: 'set a user in statut private', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 403: {description : 'forbidden'}, + 500: {description: 'Internal server error'}, + } + } + }, + } + }, ]); } }; \ No newline at end of file diff --git a/test/integration/friend.test.js b/test/integration/friend.test.js new file mode 100644 index 0000000..3709383 --- /dev/null +++ b/test/integration/friend.test.js @@ -0,0 +1,400 @@ +'use strict'; +const Hapi = require('@hapi/hapi'); +const Friend = require("../../lib/domain/model/Friend") +const User = require("../../lib/domain/model/User") +const strategy = require("../../lib/infrastructure/config/strategy"); +const Jwt = require("@hapi/jwt"); +const jwt = require('jsonwebtoken'); +require('dotenv').config() +let server +const mockUserRepository = {} +const mockFriendRepository = {} +const mockMailRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const mockToken = jwt.sign({ + sub: 'my-sub', + value: 1, + aud: 'urn:audience:test', + iss: 'urn:issuer:test', + expiresIn: '365d' +}, process.env.SECRET_ENCODER) + +describe('friend route', () => { + + beforeEach(async () => { + + server = Hapi.server({ + port: process.env.PORT || 3000 + }); + server.app.serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + mailRepository: mockMailRepository, + accessTokenManager:mockAccesTokenManager + } + server.register(Jwt) + server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + await server.register([ + require('../../lib/interfaces/routes/friends'), + ]); + }); + + afterEach(async () => { + jest.clearAllMocks(); + await server.stop(); + }); + + describe("/amis/follow", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.persist = jest.fn((test) => { + return test + }) + mockMailRepository.send = jest.fn(option => null) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + + }) + it('should respond code 200 with token user', async () => { + const res = await server.inject({ + method: 'POST', + url: '/amis/follow', + payload: { + amiIdUtilisateur: 2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + + expect(res.statusCode).toBe(200); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.persist).toHaveBeenCalledTimes(1) + expect(mockMailRepository.send).toHaveBeenCalledTimes(1) + }); + it('should respond code 400 with invalid id friend', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + if(id > 0) return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: false}) + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/follow', + payload: { + amiIdUtilisateur: -2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(400); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.persist).toHaveBeenCalledTimes(0) + expect(mockMailRepository.send).toHaveBeenCalledTimes(0) + }); + it('should respond code 403 with already existing frindship', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.persist = jest.fn((f)=> null) + const res = await server.inject({ + method: 'POST', + url: '/amis/follow', + payload: { + amiIdUtilisateur: 2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(403); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.persist).toHaveBeenCalledTimes(1) + expect(mockMailRepository.send).toHaveBeenCalledTimes(0) + }); + + }) + + describe("/amis/unfollow", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.removeFriendById = jest.fn((id, id_ami) => { + return new Friend({id_utilisateur:id, amiIdUtilisateur:id_ami, en_attente:true}) + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + + }) + + it('should respond code 200 with token user', async () => { + + const res = await server.inject({ + method: 'POST', + url: '/amis/unfollow', + payload: { + amiIdUtilisateur: 2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + + expect(res.statusCode).toBe(200); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1); + expect(mockFriendRepository.removeFriendById).toHaveBeenCalledTimes(1) + }); + it('should respond code 400 with invalid id friend', async () => { + mockUserRepository.getByUser= jest.fn((id)=> { + if(id > 0) return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: false}) + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/unfollow', + payload: { + amiIdUtilisateur: -2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(400); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.removeFriendById).toHaveBeenCalledTimes(0) + }); + it('should respond code 403 with frindship doesn\'t exist', async () => { + mockFriendRepository.removeFriendById = jest.fn((f)=> null) + const res = await server.inject({ + method: 'POST', + url: '/amis/unfollow', + payload: { + amiIdUtilisateur: 3 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(403); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.removeFriendById).toHaveBeenCalledTimes(1) + }); + + }) + + describe("/amis/accept", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.accept = jest.fn((id, id_ami) => { + return new Friend({id_utilisateur:id, amiIdUtilisateur:id_ami, en_attente:true}) + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + it('should respond code 200 with token user and friend', async () => { + const res = await server.inject({ + method: 'POST', + url: '/amis/accept', + payload: { + amiIdUtilisateur: 2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + + expect(res.statusCode).toBe(200); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.accept).toHaveBeenCalledTimes(1) + }); + it('should respond code 400 with invalid id friend', async () => { + mockUserRepository.getByUser= jest.fn((id)=> { + if(id > 0) return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: false}) + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/accept', + payload: { + amiIdUtilisateur: -2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(400); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.accept).toHaveBeenCalledTimes(0) + }); + it('should respond code 403 with frindship doesn\'t exist', async () => { + mockFriendRepository.accept = jest.fn((f)=> null) + const res = await server.inject({ + method: 'POST', + url: '/amis/accept', + payload: { + amiIdUtilisateur: 2 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(403); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.accept).toHaveBeenCalledTimes(1) + }); + + }) + + describe("/amis/profil", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.getById = jest.fn((id, id_ami) => { + return new Friend({amiIdUtilisateur:id_ami, id_utilisateur:id, en_attente: false}) + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }); + + it('should respond code 400 with invalid id friend', async () => { + mockUserRepository.getByUser= jest.fn((id)=> { + if(id > 0) return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: false}) + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/profil', + payload: { + amiIdUtilisateur: -1 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + console.log(res) + expect(res.statusCode).toBe(400); + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(3) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(0) + }); + it('should respond code 200', async () => { + mockUserRepository.getByUser= jest.fn((id)=> { + if(id > 0) return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: false}) + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/profil', + payload: { + amiIdUtilisateur: 3 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + console.log(res) + expect(res.statusCode).toBe(200); + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(3) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(1) + }); + it('should respond code 403 with frindship doesn\'t exist', async () => { + mockFriendRepository.getById = jest.fn((id, id_ami)=> { + return null + }) + const res = await server.inject({ + method: 'POST', + url: '/amis/profil', + payload: { + amiIdUtilisateur: 3 + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res.statusCode).toBe(403); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(3) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(1) + }); + + }) + + describe("/amis/list", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.getListFriendsById = jest.fn((id) => { + return [ + new User({pseudo:"test", id_utilisateur:1, email: "test@gmail.com", alias: "testt", is_private: true}), + new User({pseudo:"test2", id_utilisateur:2, email: "test2@gmail.com", alias: "testt2", is_private: false}) + ] + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + + it('should respond code 200 with token user', async () => { + const res = await server.inject({ + method: 'GET', + url: '/amis/list', + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + + expect(res.statusCode).toBe(200); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getListFriendsById).toHaveBeenCalledTimes(1) + }); + }) + + describe("/amis/request", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach( async ()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return new User({pseudo:"test", id_utilisateur:id, email: "test@gmail.com", alias: "testt", is_private: true}) + }) + mockFriendRepository.getRequestFriendsById = jest.fn((id) => { + return [ + new User({pseudo:"test", id_utilisateur:2, email: "test@gmail.com", alias: "testt", is_private: true}), + new User({pseudo:"test2", id_utilisateur:3, email: "test2@gmail.com", alias: "testt2", is_private: true}) + ] + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + it('should respond code 200 with token user', async () => { + const res = await server.inject({ + method: 'GET', + url: '/amis/request', + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + + expect(res.statusCode).toBe(200); + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getRequestFriendsById).toHaveBeenCalledTimes(1) + }); + }) +}); diff --git a/test/integration/users.test.js b/test/integration/users.test.js index ff38a06..5e03b05 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -25,7 +25,6 @@ mockAccesTokenManager.generate = ((test) =>{return ''}) describe('user route', () => { beforeEach(async () => { - server = Hapi.server({ port: process.env.PORT || 3000 }); @@ -401,7 +400,6 @@ describe('user route', () => { }) }) describe("/users/follow", ()=>{ - it("should return valid code 200",async ()=>{ mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) mockUserRepository.getByUser = jest.fn(() => "something") @@ -565,4 +563,28 @@ describe('user route', () => { }) }) + describe("/users/status", ()=>{ + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + mockUserRepository.changePrivateStatus = jest.fn((id)=>{ + return new User({ + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + password : "hjkklllllm", + id_role : 1, + + }) + }); + it("should return valid code 200",async ()=>{ + const res = await server.inject({ + method: 'POST', + url: '/users/status', + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(200); + expect(mockUserRepository.changePrivateStatus).toHaveBeenCalledTimes(1); + }) + }) }); diff --git a/test/unit/application/usecase/friend/acceptResquestUser.test.js b/test/unit/application/usecase/friend/acceptResquestUser.test.js new file mode 100644 index 0000000..97311b6 --- /dev/null +++ b/test/unit/application/usecase/friend/acceptResquestUser.test.js @@ -0,0 +1,86 @@ +const acceptRequestUser = require('../../../../../lib/application/use_cases/friend/acceptRequestUser') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const relation = { + id_utilisateur: 1, + amiIdUtilisateur: 2, + en_attente: false, + createdAt: undefined, + updatedAt: undefined, + type : 'amis' +} +const user = { + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} + +describe("acceptRequestUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + user.id_utilisateur = id + return user + }) + mockFriendRepository.accept = jest.fn((id, id_ami) => { + return relation + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + const serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + accessTokenManager:mockAccesTokenManager + } + it('should accept a request friend of a user', async () => { + + const user = await acceptRequestUser("testtoken",2,serviceLocator) + expect(user).toBe(relation) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.accept).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid id friend', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + return null + }) + const error = await catchError(async ()=>{ + await acceptRequestUser("testtoken", -2,serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + }) + + it('should throw error 403 friendship doesn\'t exist', async () => { + mockFriendRepository.accept = jest.fn((id, id_ami)=> { + return null + }) + const error = await catchError(async ()=>{ + await acceptRequestUser("testtoken", 3,serviceLocator) + }) + expect(error.code).toBe(403) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.accept).toHaveBeenCalledTimes(1) + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/friend/followUser.test.js b/test/unit/application/usecase/friend/followUser.test.js new file mode 100644 index 0000000..85847fe --- /dev/null +++ b/test/unit/application/usecase/friend/followUser.test.js @@ -0,0 +1,92 @@ +const followUser = require('../../../../../lib/application/use_cases/friend/followUser') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockMailRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const relation = { + id_utilisateur: 1, + amiIdUtilisateur: 2, + en_attente: true, + createdAt: undefined, + updatedAt: undefined, + type : 'amis' +} +const user = { + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} +describe("followUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id) => { + user.id_utilisateur = id + return user + }) + mockFriendRepository.persist = jest.fn((friend) => { + return friend + }) + mockMailRepository.send = jest.fn(option => null) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + const serviceLocator = { + userRepository: mockUserRepository, + mailRepository: mockMailRepository, + friendRepository: mockFriendRepository, + accessTokenManager:mockAccesTokenManager + } + it('should return friendship', async () => { + + const res = await followUser("testtoken",2,serviceLocator) + expect(res).toEqual(relation) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.persist).toHaveBeenCalledTimes(1) + expect(mockMailRepository.send).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid id friend', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + if (id != -1) return user + return null + }) + const error = await catchError(async ()=>{ + await followUser("testtoken",-1,serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + }); + + it('should throw error 403 friendship exist', async () => { + mockFriendRepository.persist = jest.fn((user)=> { + return null + }) + const error = await catchError(async ()=>{ + await followUser("testtoken",2,serviceLocator) + }) + expect(error.code).toBe(403) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.persist).toHaveBeenCalledTimes(1) + }); +}) \ No newline at end of file diff --git a/test/unit/application/usecase/friend/getListFriends.test.js b/test/unit/application/usecase/friend/getListFriends.test.js new file mode 100644 index 0000000..13f2128 --- /dev/null +++ b/test/unit/application/usecase/friend/getListFriends.test.js @@ -0,0 +1,102 @@ +const getListFriends = require('../../../../../lib/application/use_cases/friend/getListFriends') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const friends = [ + { + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' + },{ + id_utilisateur : 2, + pseudo : "pseudotest", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' + } +] +const user = { + id_utilisateur : 3, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} +describe("getListFriends", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id => user)) + mockFriendRepository.getListFriendsById = jest.fn((user) => { + return friends + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + const serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + accessTokenManager:mockAccesTokenManager + } + it('should return list friends of a user', async () => { + + const user = await getListFriends("testtoken",serviceLocator) + expect(user).toBe(friends) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getListFriendsById).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid token user', async () => { + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: -1}}) + mockUserRepository.getByUser = jest.fn((id)=> { + return null + }) + const error = await catchError(async ()=>{ + await getListFriends("testtoken", serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + }); + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/friend/getListFriendsRequest.test.js b/test/unit/application/usecase/friend/getListFriendsRequest.test.js new file mode 100644 index 0000000..9622cb6 --- /dev/null +++ b/test/unit/application/usecase/friend/getListFriendsRequest.test.js @@ -0,0 +1,86 @@ +const getListFriendsRequest = require('../../../../../lib/application/use_cases/friend/getListFriendsRequest') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const friends = [ + { + id_utilisateur : 4, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' + } +] +const user = { + id_utilisateur : 3, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} +describe("getListFriendsRequest", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id => user)) + mockFriendRepository.getRequestFriendsById = jest.fn((id) => { + return friends + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + + }) + + const serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + accessTokenManager: mockAccesTokenManager + } + it('should return request friends of a user', async () => { + const users = await getListFriendsRequest("testtoken",serviceLocator) + expect(users).toBe(friends) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getRequestFriendsById).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid token user', async () => { + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: -1}}) + mockUserRepository.getByUser = jest.fn((id)=> { + return null + }) + const error = await catchError(async ()=>{ + await getListFriendsRequest("testtoken", serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getRequestFriendsById).toHaveBeenCalledTimes(0) + }); + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/friend/getProfilFriend.test.js b/test/unit/application/usecase/friend/getProfilFriend.test.js new file mode 100644 index 0000000..88eb50a --- /dev/null +++ b/test/unit/application/usecase/friend/getProfilFriend.test.js @@ -0,0 +1,108 @@ +const getProfilFriend = require('../../../../../lib/application/use_cases/friend/getProfilFriend') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const relation = { + id_utilisateur: 1, + amiIdUtilisateur: 2, + en_attente: true, + createdAt: undefined, + updatedAt: undefined, + type : 'amis' +} +const user = { + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} + +const friend = { + id_utilisateur : 2, + pseudo : "pseudo22", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} + +describe("getProfilFriend", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + return user.id_utilisateur == id ? user : friend + }) + mockFriendRepository.getById = jest.fn((id, id_ami) => { + return relation + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + const serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + accessTokenManager: mockAccesTokenManager + } + it('should accept a request friend of a user', async () => { + + const res = await getProfilFriend("testtoken",2,serviceLocator) + expect(res).toBe(friend) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid id friend', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + return null + }) + const error = await catchError(async ()=>{ + await getProfilFriend("testtoken", -2,serviceLocator) + }) + expect(error.code).toBe(400) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(0) + }) + + it('should throw error 403 friendship doesn\'t exist', async () => { + mockFriendRepository.getById = jest.fn((id, id_ami)=> { + return null + }) + const error = await catchError(async ()=>{ + await getProfilFriend("testtoken", 3,serviceLocator) + }) + expect(error.code).toBe(403) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockFriendRepository.getById).toHaveBeenCalledTimes(1) + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/friend/unfollowUser.test.js b/test/unit/application/usecase/friend/unfollowUser.test.js new file mode 100644 index 0000000..8fc7092 --- /dev/null +++ b/test/unit/application/usecase/friend/unfollowUser.test.js @@ -0,0 +1,79 @@ +const unfollowUser = require('../../../../../lib/application/use_cases/friend/unfollowUser') +const catchError = require("../utils/catchError") +const mockFriendRepository = {} +const mockUserRepository = {} +const mockAccesTokenManager = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const relation = { + id_utilisateur: undefined, + amiIdUtilisateur: undefined, + en_attente: false, + createdAt: undefined, + updatedAt: undefined, + type : 'amis' +} +const user = { + id_utilisateur : 1, + pseudo : "pseudo", + email : "test@test.fr", + alias : undefined, + photo : undefined, + photo_temporaire : undefined, + token : undefined, + refresh_token : undefined, + reset_token : undefined, + password : "hjkklllllm", + id_role : 1, + ban_until : undefined, + confirmed: undefined, + confirm_token : undefined, + is_private : true , + type : 'user' +} +describe("unfollowUser", ()=>{ + afterEach(()=>{ + jest.clearAllMocks(); + }) + beforeEach(()=>{ + mockUserRepository.getByUser = jest.fn((id)=> { + user.id_utilisateur=id + return user + }) + mockFriendRepository.removeFriendById = jest.fn((id, id_ami) => { + relation.id_utilisateur = id + relation.amiIdUtilisateur = id_ami + return relation + }) + mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) + }) + + const serviceLocator = { + userRepository: mockUserRepository, + friendRepository: mockFriendRepository, + accessTokenManager:mockAccesTokenManager + } + it('should remove a friend of a user', async () => { + const user = await unfollowUser("testtoken",2,serviceLocator) + expect(user).toBe(relation) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(2) + expect(mockFriendRepository.removeFriendById).toHaveBeenCalledTimes(1) + }); + + it('should throw error 400 invalid id friend', async () => { + mockUserRepository.getByUser = jest.fn((id)=> { + if (id < 0) return user + return null + }) + const error = await catchError(async ()=>{ + await unfollowUser("testtoken",-2,serviceLocator) + }) + console.log(error) + expect(error.code).toBe(400) + expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) + expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) + }); + + +}) \ No newline at end of file From 8c7b484fa578a99c0daa84dffe9be68f51681796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:45:20 +0100 Subject: [PATCH 34/67] Feature/review-endpoints (#36) * save * pause on review * save * pause on review * CHANGE * save * getUserReviews * test elaboration * test correction * test * test correction & folder reorder * added untracked files --- .../use_cases/review/deleteReview.js | 6 + lib/application/use_cases/review/getReview.js | 12 + .../use_cases/review/getReviewLikes.js | 12 + .../use_cases/review/getReviews.js | 16 + .../use_cases/review/getUserReviews.js | 27 + .../use_cases/review/likeReview.js | 10 + lib/application/use_cases/review/putReview.js | 21 + .../use_cases/review/util/getReview.js | 18 + lib/application/use_cases/spotify/Search.js | 1 + lib/domain/entity/ReviewEntity.js | 76 +++ lib/domain/model/Album.js | 2 +- lib/domain/model/Artist.js | 2 +- lib/domain/model/Review.js | 16 + lib/domain/model/ReviewPublic.js | 13 + lib/domain/model/UserPublic.js | 1 + lib/infrastructure/config/service-locator.js | 2 + .../orm/sequelize/models/TypeReview.js | 2 +- lib/infrastructure/orm/sequelize/sequelize.js | 28 +- .../repositories/FriendRepository.js | 15 + .../repositories/ReviewRepository.js | 228 +++++++++ .../repositories/SpotifyRepository.js | 9 + .../interfaces/FriendRepositoryAbstract.js | 5 +- .../interfaces/ReviewRepositoryAbstract.js | 41 ++ .../interfaces/SpotifyRepositoryAbstract.js | 4 +- lib/infrastructure/webserver/server.js | 1 + .../controllers/ReviewController.js | 101 ++++ lib/interfaces/controllers/UsersController.js | 1 - lib/interfaces/routes/review.js | 207 ++++++++ lib/interfaces/serializers/AlbumSerializer.js | 2 +- .../serializers/ArtistSerializer.js | 2 +- .../serializers/ReviewSerializer.js | 24 + .../serializers/SerializeSearchItem.js | 10 +- lib/interfaces/serializers/TrackSerializer.js | 1 + package-lock.json | 20 +- scripts/insert_test_data.py | 276 ++++++++++ .../fixture/review/getReviewFixture.js | 48 ++ .../fixture/review/getReviewLikesFixture.js | 28 + test/integration/review.test.js | 481 ++++++++++++++++++ .../usecase/review/deleteReview.test.js | 50 ++ .../review/fixture/getReviewFixture.js | 120 +++++ .../review/fixture/getReviewLikesFixture.js | 28 + .../review/fixture/putReviewFixture.js | 88 ++++ .../usecase/review/getReview.test.js | 101 ++++ .../usecase/review/getReviewLikes.test.js | 88 ++++ .../usecase/review/getReviews.test.js | 35 ++ .../usecase/review/getUserReviews.test.js | 86 ++++ .../usecase/review/likeReview.test.js | 50 ++ .../usecase/review/putReview.test.js | 108 ++++ .../usecase/spotify/getAlbum.test.js | 6 +- .../serializers/fixtures/albumFixture.js | 54 +- .../serializers/fixtures/albumTrackFixture.js | 54 +- .../fixtures/artistAlbumsFixture.js | 108 +--- .../serializers/fixtures/artistFixture.js | 18 +- 53 files changed, 2518 insertions(+), 245 deletions(-) create mode 100644 lib/application/use_cases/review/deleteReview.js create mode 100644 lib/application/use_cases/review/getReview.js create mode 100644 lib/application/use_cases/review/getReviewLikes.js create mode 100644 lib/application/use_cases/review/getReviews.js create mode 100644 lib/application/use_cases/review/getUserReviews.js create mode 100644 lib/application/use_cases/review/likeReview.js create mode 100644 lib/application/use_cases/review/putReview.js create mode 100644 lib/application/use_cases/review/util/getReview.js create mode 100644 lib/domain/entity/ReviewEntity.js create mode 100644 lib/domain/model/Review.js create mode 100644 lib/domain/model/ReviewPublic.js create mode 100644 lib/infrastructure/repositories/ReviewRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js create mode 100644 lib/interfaces/controllers/ReviewController.js create mode 100644 lib/interfaces/routes/review.js create mode 100644 lib/interfaces/serializers/ReviewSerializer.js create mode 100644 scripts/insert_test_data.py create mode 100644 test/integration/fixture/review/getReviewFixture.js create mode 100644 test/integration/fixture/review/getReviewLikesFixture.js create mode 100644 test/integration/review.test.js create mode 100644 test/unit/application/usecase/review/deleteReview.test.js create mode 100644 test/unit/application/usecase/review/fixture/getReviewFixture.js create mode 100644 test/unit/application/usecase/review/fixture/getReviewLikesFixture.js create mode 100644 test/unit/application/usecase/review/fixture/putReviewFixture.js create mode 100644 test/unit/application/usecase/review/getReview.test.js create mode 100644 test/unit/application/usecase/review/getReviewLikes.test.js create mode 100644 test/unit/application/usecase/review/getReviews.test.js create mode 100644 test/unit/application/usecase/review/getUserReviews.test.js create mode 100644 test/unit/application/usecase/review/likeReview.test.js create mode 100644 test/unit/application/usecase/review/putReview.test.js diff --git a/lib/application/use_cases/review/deleteReview.js b/lib/application/use_cases/review/deleteReview.js new file mode 100644 index 0000000..680b649 --- /dev/null +++ b/lib/application/use_cases/review/deleteReview.js @@ -0,0 +1,6 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (idReview, userToken, {accessTokenManager, userRepository,reviewRepository})=> { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + if(!await reviewRepository.delete(idReview,id_utilisateur)) throwStatusCode(403,"ce n'est pas votre post") +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getReview.js b/lib/application/use_cases/review/getReview.js new file mode 100644 index 0000000..f9e21dd --- /dev/null +++ b/lib/application/use_cases/review/getReview.js @@ -0,0 +1,12 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +const getReview = require("./util/getReview") +module.exports = async (idReview,userToken, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository}) => { + + const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository}) + const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) + +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getReviewLikes.js b/lib/application/use_cases/review/getReviewLikes.js new file mode 100644 index 0000000..1c32011 --- /dev/null +++ b/lib/application/use_cases/review/getReviewLikes.js @@ -0,0 +1,12 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const UserPublic = require("../../../domain/model/UserPublic") +const getReview = require("./util/getReview") + +module.exports = async (reviewId,userToken,page,pageSize,{accessTokenManager,reviewRepository,friendRepository}) =>{ + const reviewTest = await getReview(reviewId,userToken, {accessTokenManager,friendRepository,reviewRepository}) + if(userToken) { + userToken = accessTokenManager.decode(userToken)?.value + } + const users = (await reviewRepository.getLikes(userToken,reviewId,page,pageSize)).map(item => new UserPublic(item)) + return users +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getReviews.js b/lib/application/use_cases/review/getReviews.js new file mode 100644 index 0000000..6b93596 --- /dev/null +++ b/lib/application/use_cases/review/getReviews.js @@ -0,0 +1,16 @@ +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async (page,pageSize,orderByLike,userToken,{reviewRepository, spotifyRepository, accessTokenManager}) => { + if(userToken) + userToken = accessTokenManager.decode(userToken)?.value + const rawReviews = await reviewRepository.getReviews(page,pageSize,orderByLike, false,userToken) + const serializedReviews = [] + rawReviews.forEach(element => { + serializedReviews.push(serializeReview(element,spotifyRepository)) + }) + return await Promise.all(serializedReviews) +} + +const serializeReview = async (rawReview,spotifyRepository) => { + const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getUserReviews.js b/lib/application/use_cases/review/getUserReviews.js new file mode 100644 index 0000000..935c479 --- /dev/null +++ b/lib/application/use_cases/review/getUserReviews.js @@ -0,0 +1,27 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async (pseudo, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,userRepository}) => { + const testUsers = await userRepository.getByEmailOrPseudo(pseudo,pseudo) + if (testUsers?.is_private) { + let valid = false + if (userToken) { + const id = accessTokenManager.decode(userToken)?.value + if (await friendRepository.areFriends(id, testUsers.id_utilisateur)) + valid = true + } + if (!valid) + throwStatusCode(403, "l'utilisateur est en privé") + } + const reviews = await reviewRepository.getReviewByUserId(testUsers.id_utilisateur,page,pageSize, orderByLike) + const serializedReviews = [] + reviews.forEach(element => { + serializedReviews.push(serializeReview(element,spotifyRepository)) + }) + return await Promise.all(serializedReviews) +} + + +const serializeReview = async (rawReview,spotifyRepository) => { + const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/likeReview.js b/lib/application/use_cases/review/likeReview.js new file mode 100644 index 0000000..5aed82e --- /dev/null +++ b/lib/application/use_cases/review/likeReview.js @@ -0,0 +1,10 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (reviewId,userToken,{accessTokenManager,userRepository,reviewRepository}) =>{ + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + if(! await reviewRepository.doesUserLikes(id_utilisateur,reviewId)){ + await reviewRepository.likeReview(id_utilisateur,reviewId) + return true + } + await reviewRepository.unlikeReview(id_utilisateur,reviewId) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/putReview.js b/lib/application/use_cases/review/putReview.js new file mode 100644 index 0000000..3a3b988 --- /dev/null +++ b/lib/application/use_cases/review/putReview.js @@ -0,0 +1,21 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer") +module.exports = async (idOeuvre, userToken, description, note,type, {accessTokenManager, userRepository,reviewRepository,spotifyRepository})=> { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + if(await reviewRepository.getByUserAndId(idOeuvre,id_utilisateur)) throwStatusCode(403,"vous avez déjà posté une review") + const id_type_review = await reviewRepository.getTypeReviewID(type) + if(!id_type_review) throwStatusCode(401,"ce type de review n'existe pas") + const rawOeuvre = await spotifyRepository.getOeuvre(idOeuvre,type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + const ReviewRaw = { + id_oeuvre: idOeuvre, + id_utilisateur, + description, + note, + id_type_review + } + const review = await reviewRepository.persist(ReviewRaw) + return reviewSerializer(review,rawOeuvre,review.utilisateur) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/util/getReview.js b/lib/application/use_cases/review/util/getReview.js new file mode 100644 index 0000000..896b171 --- /dev/null +++ b/lib/application/use_cases/review/util/getReview.js @@ -0,0 +1,18 @@ +const throwStatusCode = require("../../utils/throwStatusCode"); + +module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository}) => { + const rawReview = await reviewRepository.getById(idReview) + if(!rawReview) + throwStatusCode(404,"la review n'existe pas") + if(rawReview.utilisateur.is_private) { + let valid = false + if(userToken) { + const id = accessTokenManager.decode(userToken)?.value + if(await friendRepository.areFriends(id,rawReview.utilisateur.id_utilisateur)) + valid = true + } + if(!valid) + throwStatusCode(403, "l'utilisateur est en privé") + } + return rawReview +} \ No newline at end of file diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index 8a5264f..c57dc27 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -36,5 +36,6 @@ module.exports = async (query,filter, limit, {spotifyRepository, userRepository} if(returnValue.length> limitSize) returnValue = returnValue.splice(0,limitSize) returnValue.push(...users) + return returnValue.map(item => SerializeSearchItem(item)) } \ No newline at end of file diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js new file mode 100644 index 0000000..15a65d6 --- /dev/null +++ b/lib/domain/entity/ReviewEntity.js @@ -0,0 +1,76 @@ +const Joi = require('@hapi/joi') +const putReview = Joi.object().keys({ + idOeuvre: Joi + .string() + .min(1) + .max(50) + .required(), + description: Joi + .string() + .min(1) + .max(1500) + .required(), + note: Joi + .number() + .integer() + .greater(-1) + .less(6) + .required(), + type: Joi + .string() + .required() + .custom((value, helpers) => { + const allowedValues = ["track", "album", "artist"] + return allowedValues.includes(value) ? value : helpers.error('any.invalid') + }) +}) +const deleteReview = Joi.object().keys({ + idReview: Joi + .string() + .min(1) + .max(50) + .required() +}) + +const getReviewParams = Joi.object().keys({ + id: Joi + .string() + .min(1) + .max(50) + .required() +}) + +const getReviews = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean().invalid(false) +}) + +const likeReviewParams = Joi.object().keys({ + id: Joi.number().required() +}) + +const likeReviewQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), +}) + +const userReviewParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) + +const userReviewQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean().invalid(false) +}) +module.exports = { + putReview, + deleteReview, + getReviewParams, + getReviews, + likeReviewParams, + likeReviewQuery, + userReviewParams, + userReviewQuery +} \ No newline at end of file diff --git a/lib/domain/model/Album.js b/lib/domain/model/Album.js index f0bf662..92a3cc0 100644 --- a/lib/domain/model/Album.js +++ b/lib/domain/model/Album.js @@ -5,7 +5,7 @@ module.exports = class { this.popularity = album.popularity this.release_date = album.release_date this.total_tracks = album.total_tracks - this.images = album.images + this.image = album.image this.spotify_url = album.spotify_url this.artists = album.artists this.tracks = album.tracks diff --git a/lib/domain/model/Artist.js b/lib/domain/model/Artist.js index c8c3a03..46254fa 100644 --- a/lib/domain/model/Artist.js +++ b/lib/domain/model/Artist.js @@ -3,7 +3,7 @@ module.exports = class { constructor(artist) { this.id = artist?.id; this.name = artist.name; - this.images = artist?.images; + this.image = artist?.image; this.popularity = artist?.popularity this.genres = artist?.genres this.spotify_url = artist?.spotify_url diff --git a/lib/domain/model/Review.js b/lib/domain/model/Review.js new file mode 100644 index 0000000..a7746c1 --- /dev/null +++ b/lib/domain/model/Review.js @@ -0,0 +1,16 @@ +module.exports = class { + constructor(rawReview, utilisateur,type) { + + this.id_review = rawReview.id_review + this.id_oeuvre = rawReview.id_oeuvre + this.countlikes = rawReview.dataValues.countLike + this.countComment = rawReview.dataValues.countComment + this.description = rawReview.description + this.note = rawReview.note + this.created_at = rawReview.createdAt + this.updated_at = rawReview.updatedAt + this.type = type + + this.utilisateur = utilisateur + } +} \ No newline at end of file diff --git a/lib/domain/model/ReviewPublic.js b/lib/domain/model/ReviewPublic.js new file mode 100644 index 0000000..4a7bf8d --- /dev/null +++ b/lib/domain/model/ReviewPublic.js @@ -0,0 +1,13 @@ +module.exports = class { + constructor(rawReview,oeuvre,utilisateur) { + this.id_review = rawReview.id_review + this.description = rawReview.description + this.countlikes = rawReview.countlikes + this.countComment = rawReview.countComment + this.note = rawReview.note + this.created_at = rawReview.createdAt + this.oeuvre = oeuvre + this.utilisateur = utilisateur + this.type = rawReview.type + } +} \ No newline at end of file diff --git a/lib/domain/model/UserPublic.js b/lib/domain/model/UserPublic.js index 80cddab..b513a63 100644 --- a/lib/domain/model/UserPublic.js +++ b/lib/domain/model/UserPublic.js @@ -10,6 +10,7 @@ module.exports = class { this.photo_temporaire = userRaw?.photo_temporaire this.id_role = userRaw?.id_role; this.ban_until = userRaw?.ban_until + this.is_private = userRaw?.is_private this.type = 'user' } diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index f163518..d6f76dc 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -9,6 +9,7 @@ const documentRepository= require('../repositories/DocumentRepository'); const NodemailerRepository= require('../repositories/NodemailerRepository'); const FollowRepository= require('../repositories/FollowRepository'); const FriendRepository= require('../repositories/FriendRepository'); +const ReviewRepository= require('../repositories/ReviewRepository'); function buildBeans() { return { @@ -19,6 +20,7 @@ function buildBeans() { mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), followRepository: new FollowRepository(), friendRepository: new FriendRepository(), + reviewRepository: new ReviewRepository() }; } diff --git a/lib/infrastructure/orm/sequelize/models/TypeReview.js b/lib/infrastructure/orm/sequelize/models/TypeReview.js index ff3e1ba..00e8285 100644 --- a/lib/infrastructure/orm/sequelize/models/TypeReview.js +++ b/lib/infrastructure/orm/sequelize/models/TypeReview.js @@ -4,7 +4,7 @@ module.exports = (sequelize) => { return sequelize.define('type_review', { // attributes - id_type_teview: { + id_type_review: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index 48a3032..b77fa91 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -39,7 +39,8 @@ UserModel.belongsToMany(CommentaireModel, { as: 'user_like_comment', foreignKey: 'id_utilisateur', - through : 'like_commentaire' + through : 'like_commentaire', + onDelete: "cascade" } ) @@ -68,7 +69,8 @@ ArtisteModel.belongsToMany(UserModel, UserModel.belongsToMany(ReviewModel,{ as: 'user_like_review', foreignKey: 'id_utilisateur', - through : 'like_review' + through : 'like_review', + onDelete: "cascade" }) ReviewModel.belongsToMany(UserModel,{ as: 'review_like', @@ -90,7 +92,8 @@ ReviewModel.belongsTo(TypeReviewModel, { ReviewModel.hasMany(CommentaireModel, { as:'comment_review', - foreignKey:'id_review' + foreignKey:'id_review', + onDelete: "cascade" }) CommentaireModel.belongsTo(ReviewModel,{ as:'review_comment', @@ -101,7 +104,12 @@ CommentaireModel.belongsTo(UserModel,{ as:'user_review', foreignKey:'id_utilisateur' }) -CommentaireModel.hasMany(CommentaireModel,{ as: 'reponse', foreignKey: 'id_reponse' }) +CommentaireModel.hasMany(CommentaireModel,{ + as: 'reponse', + foreignKey: 'id_reponse', + onDelete: "cascade" + +}) RoleModel.sync() @@ -116,4 +124,16 @@ RoleModel.sync() console.error('Erreur dans la création des roles : '+error) }) +TypeReviewModel.sync() + .then(()=>{ + return TypeReviewModel.bulkCreate([ + {id_type_review: 1, libelle : 'track'}, + {id_type_review: 2, libelle : 'album'}, + {id_type_review: 3, libelle : 'artist'} + ]) + }) + .then(() => console.log('Creation des types de reviews réussi')) + .catch((error) => { + console.error('Erreur dans la création des types de reviews : '+error) + }) module.exports = sequelize; \ No newline at end of file diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index bb231f8..9068bcf 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -96,4 +96,19 @@ module.exports = class extends FriendRepositoryAbstract { return this.createAmis(user); } + async areFriends(id, amiIdUtilisateur) { + const count = await this.model.count({ + where: { + [Op.or]: [ + { id_utilisateur: id, + amiIdUtilisateur: amiIdUtilisateur + }, + { id_utilisateur: amiIdUtilisateur, + amiIdUtilisateur: id + }, + ] + }, + }); + return count === 2 + } }; diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js new file mode 100644 index 0000000..8fdc876 --- /dev/null +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -0,0 +1,228 @@ +'use strict'; + +const sequelize = require('../orm/sequelize/sequelize'); +const ReviewRepository = require('./interfaces/ReviewRepositoryAbstract'); +const { Op} = require('sequelize'); +const User = require("../../domain/model/User"); +const Review = require("../../domain/model/Review"); +module.exports = class extends ReviewRepository { + + constructor() { + super(); + this.db = sequelize; + this.review = this.db.model('review'); + this.TypeReview = this.db.model('type_review'); + this.user = this.db.model('utilisateur'); + this.likeReviewModel = this.db.model('like_review') + } + createReview(seqReview) { + if(!seqReview) return null + const type = seqReview.type_review.dataValues.libelle + return new Review(seqReview,new User(seqReview.utilisateur),type) + } + async persist(ReviewEntity) { + let seqReview = await this.review.create(ReviewEntity, { + include: [{ + model: this.user, + as: "utilisateur", + attributes: ['alias'] + }] + }); + return this.getById(seqReview.id_review) + } + async getByUserAndId(id_oeuvre,id_utilisateur) { + return await this.review.findOne({ + where: { + id_oeuvre, + id_utilisateur + } + }) + } + async getTypeReviewID(label){ + const seqTypeReview = await this.TypeReview.findOne({ + where: { + libelle: label + } + }); + return seqTypeReview?.id_type_review + + } + + async delete(id_review,id_utilisateur) { + const seqReview = await this.review.findOne({ + where: { + id_review, + id_utilisateur + } + }); + if(!seqReview) return false + await this.review.destroy({ + where: { + id_review, + id_utilisateur + } + }); + return true + } + async getById(id_review){ + const seqReview = await this.review.findOne({ + where: { + id_review: id_review + }, + attributes: { + include: [ + [ + sequelize.literal('(SELECT COUNT(DISTINCT like_review.id_utilisateur ) FROM like_review WHERE like_review.id_review = review.id_review)'), + 'countLike' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), + 'countComment' + ], + ] + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review" + } + ] + + }); + return this.createReview(seqReview) + } + + async getReviews(page,pageSize,orderByLike, fetchPrivate, id) { + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + let whereClause = {} + if(!fetchPrivate) { + whereClause.id_utilisateur = id + ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} + : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} + } + + const reviews = await this.review.findAll({ + limit: pageSize, + offset: offset, + attributes: { + include: [ + [ + sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), + 'countLike' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), + 'countComment' + ], + ] + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review" + } + ], + order, + where: whereClause, + }) + return reviews.map(item => this.createReview(item)) + } + async doesUserLikes(id_utilisateur,id_review) { + const count = await this.likeReviewModel.count({ + where: { + id_utilisateur, + id_review + }, + }); + return count > 0 + } + async likeReview(id_utilisateur,id_review) { + const seqLike = await this.likeReviewModel.create({ + id_review, + id_utilisateur + }); + await seqLike.save(); + } + async unlikeReview(id_utilisateur,id_review) { + await this.likeReviewModel.destroy({ + where: { + id_utilisateur, + id_review + } + }) + } + async getLikes(id_utilisateur,id_review,page,pageSize) { + const offset = (page - 1) * pageSize; + const whereClause = {} + whereClause['$review_like->like_review.id_review$'] = id_review + whereClause.id_utilisateur = id_utilisateur + ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))`)} + : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} + const reviews = await this.review.findAll({ + offset: offset, + include: [ + { + model: this.user, + as: "review_like", + where: whereClause, + }, + ], + + }) + const limit = reviews[0]?.review_like.length > pageSize ? pageSize : reviews[0]?.review_like.length + return reviews[0]?.review_like ? reviews[0]?.review_like.slice(0,limit) : [] + } + + async getReviewByUserId(id_utilisateur,page,pageSize,orderByLike) { + const offset = (page - 1) * pageSize; + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + + const reviews = await this.review.findAll({ + offset: offset, + limit: pageSize, + attributes: { + include: [ + [ + sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), + 'countLike' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), + 'countComment' + ], + ] + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review" + } + ], + where: { + id_utilisateur + }, + order + }) + console.log(reviews) + return reviews.map(item => this.createReview(item)) + } +}; + diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index b118112..b56f7bc 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -158,4 +158,13 @@ module.exports = class extends spotifyRepositoryAbstract{ return response.data }) } + getOeuvre(id,type) { + switch (type) { + case 'artist': return this.getSpotifyArtist(id) + case 'album': return this.getSpotifyAlbums(id) + case 'track': return this.getSpotifyTracks(id) + default: + return {error: {status: 404, message: 'ressource not found'}} + } + } }; diff --git a/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js index 88ba22d..8382a30 100644 --- a/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js @@ -23,5 +23,8 @@ module.exports = class { accept(id, amiIdUtilisateur) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - + + areFriends(id, amiIdUtilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js new file mode 100644 index 0000000..6baf51d --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js @@ -0,0 +1,41 @@ +'use strict'; + +module.exports = class { + + persist(review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getByUserAndId(id_oeuvre,id_utilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + delete(id_oeuvre,id_utilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getTypeReviewID(label){ + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getById(id_review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getReviews(page,pageSize,orderByLike) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + doesUserLikes(id_utilisateur,id_review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + likeReview(id_utilisateur,id_review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + unlikeReview(id_utilisateur,id_review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getLikes(id_utilisateur,id_review,page,pageSize) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getReviewByUserId(id_utilisateur,pageSize,orderByLike) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } +}; diff --git a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js index 9b29c40..bc78173 100644 --- a/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/SpotifyRepositoryAbstract.js @@ -26,6 +26,8 @@ module.exports = class { async getSpotifyArtist(id){ throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } - + getOeuvre(id) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 26c3a5c..3f61be7 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -83,6 +83,7 @@ const createServer = async () => { await server.register([ require('../../interfaces/routes/users'), require('../../interfaces/routes/spotify'), + require('../../interfaces/routes/review'), require('../../interfaces/routes/upload'), require('../../interfaces/routes/friends'), ]); diff --git a/lib/interfaces/controllers/ReviewController.js b/lib/interfaces/controllers/ReviewController.js new file mode 100644 index 0000000..20422c6 --- /dev/null +++ b/lib/interfaces/controllers/ReviewController.js @@ -0,0 +1,101 @@ +const putReview = require("../../application/use_cases/review/putReview"); +const deleteReview = require("../../application/use_cases/review/deleteReview"); +const getReview = require("../../application/use_cases/review/getReview"); +const getReviews = require("../../application/use_cases/review/getReviews"); +const likeReview = require("../../application/use_cases/review/likeReview"); +const getReviewLikes = require("../../application/use_cases/review/getReviewLikes"); +const getUserReviews = require("../../application/use_cases/review/getUserReviews"); +const handleError = require("./utils/handleError"); + +module.exports = { + + async putReview(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {idOeuvre, note, description,type} = request.payload + + return handler.response(await putReview(idOeuvre, token, description, note,type, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async deleteReview(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {idReview} = request.payload + await deleteReview(idReview, token, serviceLocator) + return handler.response('').code(200) + }catch(error){ + return handleError(error) + } + }, + async getReview(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const {id} = request.params + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + return handler.response(await getReview(id,token, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async getReviews(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const {page, pageSize, orderByLike} = request.query + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + return handler.response(await getReviews(page,pageSize,orderByLike,token, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async likeReview(request, handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const {id} = request.params + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + await likeReview(id,token,serviceLocator) + return handler.response('').code(200) + }catch(error){ + return handleError(error) + } + }, + async getLikes(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {page, pageSize} = request.query + return handler.response(await getReviewLikes(id, token,page,pageSize, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async getUserReviews(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {page, pageSize,orderByLike} = request.query + return handler.response(await getUserReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, +} \ No newline at end of file diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index ea2f6b9..2f74d46 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -137,7 +137,6 @@ module.exports = { const serviceLocator = request.server.app.serviceLocator; const authorizationHeader = request.headers.authorization; const [, token] = authorizationHeader.split(' '); - console.log(token) const {artistId} = request.payload try{ const returnValue = await follow(token,artistId,serviceLocator) diff --git a/lib/interfaces/routes/review.js b/lib/interfaces/routes/review.js new file mode 100644 index 0000000..38bdd65 --- /dev/null +++ b/lib/interfaces/routes/review.js @@ -0,0 +1,207 @@ +const ReviewController = require("../controllers/ReviewController"); +const { + putReview, + deleteReview, + getReviews, + getReviewParams, + likeReviewParams, + likeReviewQuery, + userReviewParams, + userReviewQuery +} = require("../../domain/entity/ReviewEntity"); +module.exports = { + name: 'review', + version: '1.0.0', + register: async (server) => { + server.route([ + { + method: 'PUT', + path: '/review', + handler: ReviewController.putReview, + options: { + auth: 'jwt', + description: 'create a review', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: putReview + } + }, + }, + { + method: 'DELETE', + path: '/review', + handler: ReviewController.deleteReview, + options: { + auth: 'jwt', + description: 'delete a review', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: deleteReview + } + }, + }, + { + method: 'GET', + path: '/review/{id}', + handler: ReviewController.getReview, + options: { + description: 'get a review', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: getReviewParams, + } + } + }, + { + method: 'GET', + path: '/reviews', + handler: ReviewController.getReviews, + options: { + description: 'get review list', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + query: getReviews + } + } + }, + { + method: 'POST', + path: '/review/{id}/like', + handler: ReviewController.likeReview, + options: { + auth: "jwt", + description: 'like a review', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: likeReviewParams + } + } + }, + { + method: 'GET', + path: '/review/{id}/likes', + handler: ReviewController.getLikes, + options: { + description: 'get review\' likes', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: likeReviewParams, + query: likeReviewQuery + } + } + }, + { + method: 'GET', + path: '/reviews/user/{id}', + handler: ReviewController.getUserReviews, + options: { + description: 'get review\' likes', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: userReviewParams, + query: userReviewQuery + } + } + } + //GET USER LIKES MAYBE + ]); + } +}; \ No newline at end of file diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index 1ec7207..65306e5 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -9,7 +9,7 @@ const serializeAlbum = (albumRaw) => { total_tracks: albumRaw.total_tracks, spotify_url : albumRaw.external_urls.spotify, name: albumRaw.name, - images: albumRaw.images, + image: albumRaw?.images ? albumRaw.images[0].url : null, release_date : albumRaw.release_date, artists : albumRaw?.artists?.map(item => SerializeArtist(item)), tracks: tracks, diff --git a/lib/interfaces/serializers/ArtistSerializer.js b/lib/interfaces/serializers/ArtistSerializer.js index 2bd457b..8d59230 100644 --- a/lib/interfaces/serializers/ArtistSerializer.js +++ b/lib/interfaces/serializers/ArtistSerializer.js @@ -3,7 +3,7 @@ const serializeArtiste = (artisteRaw) => { const artist = { id: artisteRaw.id, name: artisteRaw.name, - images: artisteRaw?.images, + image: artisteRaw?.images ? artisteRaw.images[0].url : null, spotify_url : artisteRaw?.external_urls?.spotify, popularity: artisteRaw?.popularity, genres : artisteRaw?.genres diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js new file mode 100644 index 0000000..99205f1 --- /dev/null +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -0,0 +1,24 @@ +const Album = require("../../domain/model/Album"); +const artistSerializer = require("./ArtistSerializer"); +const trackSerializer = require("./TrackSerializer"); +const albumSerializer = require("./AlbumSerializer"); +const UserPublic = require("../../domain/model/UserPublic") +const ReviewPublic = require("../../domain/model/ReviewPublic"); +const serializeReview = (rawReview,rawOeuvre,utilisateur) => { + switch (rawReview.type){ + case 'artist': + rawOeuvre = artistSerializer(rawOeuvre) + break + case 'track': + rawOeuvre = trackSerializer(rawOeuvre) + break + case 'album': + rawOeuvre = albumSerializer(rawOeuvre) + break + default: + rawOeuvre = null + } + return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur)) +} + +module.exports = serializeReview \ No newline at end of file diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 8c6a6c1..6460ee9 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -1,7 +1,7 @@ const seralizer = { 'artist': (item) => { return { - imageURL: item.images[0]?.url, + imageURL: item.image, title: item.name, subtitle: '' } @@ -9,28 +9,28 @@ const seralizer = { }, 'album': (item) => { return { - imageURL: item.images[0]?.url, + imageURL: item.image, title: item.name, subtitle: item?.artists[0].name } }, 'track': (item) => { return { - imageURL: item.album.images[0]?.url, + imageURL: item.album.image, title: item.name, subtitle: item?.artists[0].name } }, 'single': (item) => { return { - imageURL: item.images[0]?.url, + imageURL: item.image, title: item.name, subtitle: item?.artists[0].name } }, 'compilation': (item) => { return { - imageURL: item.images[0]?.url, + imageURL: item.image, title: item.name, subtitle: item?.artists[0].name } diff --git a/lib/interfaces/serializers/TrackSerializer.js b/lib/interfaces/serializers/TrackSerializer.js index f20ac5e..ced908e 100644 --- a/lib/interfaces/serializers/TrackSerializer.js +++ b/lib/interfaces/serializers/TrackSerializer.js @@ -14,6 +14,7 @@ const serializeTrack = (trackRaw) => { duration_ms: trackRaw?.duration_ms, popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, }; + return new Track(track) }; diff --git a/package-lock.json b/package-lock.json index 2d7c252..991fc5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2855,7 +2855,8 @@ "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "node_modules/cjs-module-lexer": { "version": "1.2.3", @@ -3659,7 +3660,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/handlebars": { "version": "4.7.8", @@ -3759,6 +3761,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, "engines": { "node": ">=4" } @@ -3922,6 +3925,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -3953,6 +3957,7 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, "engines": { "node": "*" } @@ -4145,7 +4150,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isstream": { "version": "0.1.2", @@ -5016,7 +5022,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema": { "version": "0.2.3", @@ -5573,6 +5580,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, "dependencies": { "abbrev": "1" }, @@ -9209,6 +9217,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -9448,6 +9457,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -9693,6 +9703,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9762,6 +9773,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", diff --git a/scripts/insert_test_data.py b/scripts/insert_test_data.py new file mode 100644 index 0000000..d07ddb1 --- /dev/null +++ b/scripts/insert_test_data.py @@ -0,0 +1,276 @@ +import random +import requests +import mysql.connector +import faker + + +def getRandomImageURL(): + baseUrl = 'https://ozgrozer.github.io/100k-faces' + folder = random.randint(0, 9) + val = random.randint(0,999) + file = f"00{folder}{'000'[len(str(val)):] + str(val)}" + + return f"{baseUrl}/0/{folder}/{file}.jpg" +def insertUser(cursor,pseudo,password,bio): + + try: + photo = getRandomImageURL() + user_id = None + date = faker.Faker().date_between(start_date='-1y', end_date='today') + data_to_insert = { + "pseudo": pseudo, + "alias": pseudo, + "password": password, + "email": f"{pseudo}.{pseudo}@gmail.com", + "photo": photo, + "bio": bio, + "confirmed": 1, + "id_role": 1, + "createdAt": date, + "updatedAt": date, + "is_private": 1 if random.random() <0.5 else 0 + } + insert_query = """ + INSERT INTO utilisateur + (pseudo, alias, password, email, photo, bio, confirmed, id_role,createdAt,updatedAt,is_private) + VALUES (%(pseudo)s, %(alias)s, %(password)s, %(email)s, %(photo)s, %(bio)s,%(confirmed)s, %(id_role)s,%(createdAt)s, %(updatedAt)s,%(is_private)s) + """ + # Exécuter la requête d'insertion avec les données + cursor.execute(insert_query, data_to_insert) + # Récupérer l'ID de l'utilisateur créé + user_id = cursor.lastrowid + print("insert") + except Exception as err: + print(f"Erreur lors de l'insertion dans la base de données : {err}") + + return user_id +def putReponse(cursor,reponse,ids, review_ids): + reponse_ids = [] + for i in range(len(review_ids)): + i = review_ids + rid = None + for y in range(random.randint(0,5)): + date = faker.Faker().date_between(start_date='-1y', end_date='today') + data_to_insert = { + "description": random.choice(reponse), + "createdAt": date, + "updatedAt": date, + "id_utilisateur": random.choice(ids), + "id_review": review_ids[y], + "id_reponse": rid, + } + insert_reponse = """ + INSERT INTO `commentaire`(`description`, `id_review`,`id_utilisateur`,`id_reponse`, `createdAt`, `updatedAt` ) + VALUES (%(description)s,%(id_review)s,%(id_utilisateur)s,%(id_reponse)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_reponse, data_to_insert) + rid = cursor.lastrowid + reponse_ids.append(rid) + return reponse_ids +def putReview(cursor,review, ids,track_ids): + review_ids = [] + for i in range(len(ids)): + if(random.random() < 0.5): + date = faker.Faker().date_between(start_date='-1y', end_date='today') + data_to_insert = { + "description": random.choice(review), + "note": random.randint(0,5), + "createdAt": date, + "updatedAt": date, + "id_utilisateur": ids[i], + "id_oeuvre": random.choice(track_ids), + "id_type_review": 1 + } + insert_review = """ + INSERT INTO `review`(`description`, `note`, `createdAt`, `updatedAt`, `id_utilisateur`,`id_oeuvre`,`id_type_review`) + VALUES (%(description)s,%(note)s,%(createdAt)s,%(updatedAt)s,%(id_utilisateur)s,%(id_oeuvre)s,%(id_type_review)s) + """ + cursor.execute(insert_review, data_to_insert) + # Récupérer l'ID de l'utilisateur créé + review_ids.append(cursor.lastrowid) + return review_ids +def putLikesReview(cursor,ids,review_ids): + for i in range(len(review_ids)): + for y in range(len(ids)): + if(random.random() <0.2): continue + date = faker.Faker().date_between(start_date='-1y', end_date='today') + data_to_insert = { + "createdAt": date, + "updatedAt": date, + "id_utilisateur": ids[y], + "id_review": review_ids[i], + + } + insert_query = """ + INSERT INTO `like_review`(`id_utilisateur`, `id_review`, `createdAt`, `updatedAt`) + VALUES (%(id_utilisateur)s,%(id_review)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_query, data_to_insert) + + +def putLikesReponse(cursor,ids,reponse_ids): + for i in range(len(reponse_ids)): + for y in range(len(ids)): + if(random.random() <0.2): continue + date = faker.Faker().date_between(start_date='-1y', end_date='today') + data_to_insert = { + "createdAt": date, + "updatedAt": date, + "id_utilisateur": ids[y], + "id_com": reponse_ids[i], + + } + insert_query = """ + INSERT INTO `like_commentaire`(`id_utilisateur`, `id_com`, `createdAt`, `updatedAt`) + VALUES (%(id_utilisateur)s,%(id_com)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_query, data_to_insert) + +def insert_relation(cursor,ids): + validation = {} + date = faker.Faker().date_between(start_date='-1y', end_date='today') + for i in range(len(ids)): + for y in range(len(ids)): + if(i == y or random.random() < 0.4): + continue + en_attente = 1 + if(f"key-{y}-{i}" in validation): + en_attente = 0 + validation[f"key-{y}-{i}"][0]['en_attente'] = 0 + data_to_insert = { + "en_attente": en_attente, + "id_utilisateur": ids[i], + "amiIdUtilisateur": ids[y], + "createdAt": date, + "updatedAt": date, + } + insert_query = """ + INSERT INTO amis + (en_attente,id_utilisateur, amiIdUtilisateur, createdAt,updatedAt) + VALUES (%(en_attente)s, %(id_utilisateur)s, %(amiIdUtilisateur)s, %(createdAt)s, %(updatedAt)s) + """ + validation[f"key-{i}-{y}"] = [data_to_insert,insert_query] + # Récupérer l'ID de l'utilisateur créé + + for value in validation.values(): + try: + cursor.execute(value[1], value[0]) + except Exception as err: + print(f"Erreur lors de l'insertion dans la base de données : {err}") + +def obtenir_prenoms_aleatoires(nombre): + # Remplacez l'URL de l'API par celle que vous souhaitez utiliser + api_url = f"https://opendata.paris.fr/api/explore/v2.1/catalog/datasets/liste_des_prenoms/records?select=prenoms&limit=100&offset={random.randint(0,10000)}" + try: + # Effectuer la requête à l'API + reponse = requests.get(api_url) + # Vérifier si la requête a réussi (code de statut 200) + if reponse.status_code == 200: + # Extraire les prénoms de la réponse JSON + prenoms_api = reponse.json() + # Sélectionner un échantillon aléatoire de prénoms + sample = [x["prenoms"] for x in prenoms_api["results"]] + prenoms_aleatoires = random.sample(sample, min(nombre, len(sample))) + return prenoms_aleatoires + else: + print(f"Erreur lors de la requête à l'API. Code de statut : {reponse.status_code}") + except Exception as e: + print(f"Erreur lors de la requête à l'API : {e}") + +n = 50 +biographies = [ + "Explorateur du monde en 150 caractères. Passionné de voyages, de cultures et de moments uniques. #LifeExplorer", + "Artiste en herbe, créant des chefs-d'œuvre avec chaque coup de pinceau. #ArtLife #PassionCréative", + "Architecte de la vie, construisant des rêves un jour à la fois. #DreamBuilder #LifeArchitect", + "Mordu de fitness, repoussant mes limites chaque jour. #FitLife #ChallengeAccepted", + "Globe-trotteur intrépide, capturant des souvenirs partout où je vais. #Wanderlust #AdventureSeeker", + "Amateur de mots, jonglant avec les lettres pour créer des univers magiques. #WordWizard #Storyteller", + "Explorateur culinaire, découvrant de nouveaux plaisirs gustatifs. #FoodieAdventures #TasteExplorer", + "Passionné de tech, naviguant à travers le monde numérique. #TechEnthusiast #DigitalNomad", + "Danseur de la vie, rythmant chaque moment avec grâce et énergie. #DanceLife #JoyfulRhythms", + "Aventurier des étoiles, explorant l'univers avec curiosité. #Stargazer #CosmicExplorer", + "Éco-warrior, défenseur de notre planète. #GreenLiving #EarthDefender", + "Exploratrice du bien-être, cultivant l'harmonie intérieure. #WellnessJourney #MindfulLiving", + "Ingénieur du bonheur, construisant des ponts vers la joie. #HappinessEngineer #PositiveVibesOnly", + "Mélomane moderne, explorant les notes du passé et du présent. #MusicExplorer #HarmonySeeker", + "Maître des codes, tissant des histoires à travers le langage binaire. #CodeArtisan #DigitalStoryteller" +] + +# Afficher la liste complète +reviews_musicales = [ + "Une symphonie transcendante qui emporte l'auditeur dans un voyage émotionnel profond. #ChefDOeuvre #MusiqueClassique", + "Des rythmes hypnotiques qui font vibrer l'âme, une expérience musicale inoubliable. #GrooveIntense #MusiqueDuMonde", + "Des paroles poignantes qui capturent l'esprit de toute une génération. #TextesProfonds #ChansonEngagée", + "Un mélange parfait de mélodies envoûtantes et de beats entraînants. #VibesPositives #PopPerfection", + "Une performance vocale à couper le souffle, une véritable démonstration de talent. #VoixExceptionnelle #SoulfulSounds", + "Des accords de guitare envoûtants qui créent une atmosphère intime et captivante. #AcousticMagic #FolkVibes", + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "Un album qui transcende les genres, une fusion audacieuse de styles musicaux. #GenreBlending #Eclectique", + "Des harmonies célestes qui transportent l'auditeur dans un état de béatitude. #HarmonieParfaite #AmbianceCéleste", + "Des beats percutants et des paroles incisives, un manifeste musical de rébellion. #RythmesEngagés #RebelAttitude", + "Une expérience auditive immersive, chaque morceau est une aventure sensorielle unique. #SonEn3D #AudioExpérience", + "Un crescendo émotionnel qui éveille les passions les plus profondes. #ÉmotionsIntenses #CrescendoEmotionnel", + "Des arrangements subtils qui capturent l'essence de la nostalgie d'une époque révolue. #VintageVibes #NostalgieMusicale", + "Des beats entraînants qui font vibrer le corps, une invitation à la piste de danse. #RythmesContagieux #DanceFloorAnthems", + "Un tour de force musical qui transcende les générations, une œuvre intemporelle. #MusiqueÉternelle #ClassicForever" +] + +reponses_reviews = [ + "Merci pour cette superbe critique! Tellement d'accord sur l'impact émotionnel de cette symphonie. #PartageonsNosÉmotions", + "Ces rythmes m'ont aussi transporté! On devrait tous explorer davantage la diversité musicale du monde. #MusiqueUniverselle", + "Les paroles de cette chanson m'ont vraiment touché. La musique peut être une force puissante pour le changement. #PowerOfLyrics", + "D'accord à 100%! Les vibes positives de cet album sont exactement ce dont on a besoin. #SpreadJoy #MusicLover", + "Sa voix est une merveille! Tellement d'émotion derrière chaque note. #VoixInoubliable #MusicalSoulmate", + "J'aime ces accords de guitare! Ils créent une ambiance si chaleureuse et authentique. #AcousticMagic #FolkVibesForever", + "Absolument, l'innovation sonore est si rafraîchissante. On a besoin de plus d'expérimentation dans la musique! #PushingBoundaries", + "Je suis totalement d'accord avec cette analyse! La fusion de genres est une véritable prouesse artistique. #EclectiqueForever", + "Ces harmonies sont vraiment transcendantes. Chaque écoute est une expérience méditative. #MusiqueMystique #AudioPhile", + "Les paroles engagées résonnent vraiment avec moi. La musique peut être un moyen puissant de transmettre un message. #MusicActivism", + "Tu as décrit parfaitement cette expérience immersive! On se sent transporté à chaque écoute. #AudioVoyage #SonicDreams", + "Le crescendo émotionnel est époustouflant! On ressent chaque montée et descente de l'émotion. #CrescendoMagique #EmotionalJourney", + "Tellement vrai! Les arrangements subtils ajoutent une couche de nostalgie et d'authenticité. #VintageVibes #TimelessTunes", + "J'ai dansé toute la nuit sur ces beats entraînants! La musique a le pouvoir de rassembler les gens sur la piste de danse. #MusicUnity", + "L'œuvre intemporelle transcende vraiment les générations. Un chef-d'œuvre qui restera gravé dans l'histoire musicale. #ClassicMasterpiece", + "Merci pour cette critique passionnée! Il est génial de voir tant d'enthousiasme pour la musique. #MusicEnthusiast #KeepListening" +] + +track_ids = [ + "0wJoRiX5K5BxlqZTolB2LD", + "2AxCeJ6PSsBYiTckM0HLY7", + "0NWPxcsf5vdjdiFUI8NgkP", + "1Eolhana7nKHYpcYpdVcT5", + "1ntxpzIUbSsizvuAy6lTYY", + "23MrkN7g6Q5U7GLIxNHN1B", + "0auKlivXpm76wR63mMJ3pR", + "5uDpwSGjljhIgDB1ZYdp9c", + "5LI7PoHEolR8plrf3I16sq", + "5H6Jp0syB5yEPk7SWYdlmk", +] +pseudo = obtenir_prenoms_aleatoires(n) +conn = mysql.connector.connect( + host="localhost", + user="root", + password="root", + database="spotify", + port=3306 +) + +print("-------------INSERT USERS----------------------") +password = "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2" +ids = [] +cursor = conn.cursor() +for data in pseudo: + id = insertUser(cursor,data,password,random.choice(biographies)) + if(id is not None): + ids.append(id) +print("-------------INSERT RELATIONS----------------------") +insert_relation(cursor,ids) + +print("-------------INSERT REVIEWS----------------------") +review_ids = putReview(cursor,reviews_musicales,ids,track_ids) +putLikesReview(cursor,ids,review_ids) +print("-------------INSERT COMMENT----------------------") +reponse_ids = putReponse(cursor, reponses_reviews,ids,review_ids) +putLikesReponse(cursor,ids,reponse_ids) +conn.commit() + diff --git a/test/integration/fixture/review/getReviewFixture.js b/test/integration/fixture/review/getReviewFixture.js new file mode 100644 index 0000000..b465e4e --- /dev/null +++ b/test/integration/fixture/review/getReviewFixture.js @@ -0,0 +1,48 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + +} + +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUser +} + + + + + +module.exports = { + rawReview, + mockArtist, +} \ No newline at end of file diff --git a/test/integration/fixture/review/getReviewLikesFixture.js b/test/integration/fixture/review/getReviewLikesFixture.js new file mode 100644 index 0000000..c5289b5 --- /dev/null +++ b/test/integration/fixture/review/getReviewLikesFixture.js @@ -0,0 +1,28 @@ +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +module.exports = { + mockUser, + mockPublicUser +} \ No newline at end of file diff --git a/test/integration/review.test.js b/test/integration/review.test.js new file mode 100644 index 0000000..2449820 --- /dev/null +++ b/test/integration/review.test.js @@ -0,0 +1,481 @@ +'use strict'; +const Hapi = require('@hapi/hapi'); +const strategy = require("../../lib/infrastructure/config/strategy"); +const Jwt = require("@hapi/jwt"); +const jwt = require('jsonwebtoken'); + +require('dotenv').config() +let server +const mockReviewRepository = {} +const mockFriendRepository = {} +const mockAccesTokenManager = {} +const mockSpotifyRepository = {} +const mockUserRepository = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const mockToken = jwt.sign({ + sub: 'my-sub', + value: 1, + aud: 'urn:audience:test', + iss: 'urn:issuer:test', + expiresIn: '365d' +}, process.env.SECRET_ENCODER) + +describe('review route', () => { + + beforeEach(async () => { + + server = Hapi.server({ + port: process.env.PORT || 3000 + }); + server.app.serviceLocator = { + reviewRepository: mockReviewRepository, + friendRepository: mockFriendRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + userRepository: mockUserRepository + } + server.register(Jwt) + server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + await server.register([ + require('../../lib/interfaces/routes/review'), + ]); + }); + + afterEach(async () => { + jest.clearAllMocks(); + await server.stop(); + }); + const { + mockArtist, + rawReview, + } = require("./fixture/review/getReviewFixture") + describe("GET review route", ()=>{ + + it("should return status code 200", async () => { + + mockReviewRepository.getById = jest.fn((id) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1`, + }); + expect(res1.statusCode).toBe(200); + + }) + it("should return status code 404", async () => { + mockReviewRepository.getById = jest.fn((id) => null) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1`, + }); + expect(res1.statusCode).toBe(404); + + }) + it("should return status code 403", async () => { + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(403); + + }) + it("should return status code 400", async () => { + mockReviewRepository.getById = jest.fn((id) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(400); + + }) + }) + describe("GET reviews route", ()=>{ + it("should return status code 200", async ()=>{ + + mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews?page=1&pageSize=10&orderByLike=true`, + + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 200 with user login", async ()=>{ + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews?page=1&pageSize=10&orderByLike=true`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(200); + }) + }) + + describe("DELETE review route", ()=>{ + it("should return status code 200", async ()=>{ + mockReviewRepository.delete = jest.fn((id) => true) + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + const res1 = await server.inject({ + method: 'DELETE', + url: `/review`, + payload: { + idReview: 'idReview' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 401 no header", async ()=>{ + mockReviewRepository.delete = jest.fn((id) => true) + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + const res1 = await server.inject({ + method: 'DELETE', + url: `/review`, + payload: { + idReview: 'idReview' + }, + }); + expect(res1.statusCode).toBe(401); + }) + + it("should return status code 401 bad auth token", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => null) + const res1 = await server.inject({ + method: 'DELETE', + url: `/review`, + payload: { + idReview: 'idReview' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(401); + }) + it("should return status code 401 not post owner", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.delete = jest.fn((id) => false) + const res1 = await server.inject({ + method: 'DELETE', + url: `/review`, + payload: { + idReview: 'idReview' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(403); + }) + }) + + describe("GET reviewLikes route", ()=>{ + const { + mockUser, + mockPublicUser + } = require("./fixture/review/getReviewLikesFixture") + it("should return status code 404", async ()=>{ + mockReviewRepository.getById = jest.fn((id) => null) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + // headers: { + // Authorization: `Bearer ${mockToken}` + // } + }); + expect(res1.statusCode).toBe(404) + }) + it("should return status code 403", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(403) + }) + it("should return status code 200", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: false + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockReviewRepository.getLikes = jest.fn((id) => [mockUser]) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + }); + expect(res1.statusCode).toBe(200) + }) + }) + + describe("GET userReviews route", ()=>{ + it("should return status code 200", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: false + } + }) + mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews/user/{id}?page=1&pageSize=10&orderByLike=true`, + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 403", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: true + } + }) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews/user/{id}?page=1&pageSize=10&orderByLike=true`, + }); + expect(res1.statusCode).toBe(403); + }) + }) + + describe("GET reviewLike route",()=>{ + it("should return status code 200", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: false + } + } + const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockReviewRepository.getLikes = jest.fn((id) => [mockUser]) + + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 403", async ()=>{ + + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(403); + + }) + it("should return status code 404", async ()=>{ + + mockReviewRepository.getById = jest.fn((id) => null) + const res1 = await server.inject({ + method: 'GET', + url: `/review/1/likes?page=1&pageSize=10`, + }); + expect(res1.statusCode).toBe(404); + + }) + }) + + describe("PUT review route",()=>{ + it("should return status code 200", async ()=>{ + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => null) + mockReviewRepository.getTypeReviewID = jest.fn((type) => 1) + mockReviewRepository.persist = jest.fn((reviewRaw) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + const res1 = await server.inject({ + method: 'PUT', + url: `/review`, + payload: { + idOeuvre: 'idOeuvre', + description: 'description', + note: 5, + type: 'artist' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 400", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => null) + mockReviewRepository.getTypeReviewID = jest.fn((type) => 1) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: 'error' + } + } + }) + const res1 = await server.inject({ + method: 'PUT', + url: `/review`, + payload: { + idOeuvre: 'idOeuvre', + description: 'description', + note: 5, + type: 'artist' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(400); + }) + it("should return status code 401", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => null) + const res1 = await server.inject({ + method: 'PUT', + url: `/review`, + payload: { + idOeuvre: 'idOeuvre', + description: 'description', + note: 5, + type: 'artist' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(401); + }) + it("should return status code 403", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => { + return { + id_review: 1 + } + }) + const res1 = await server.inject({ + method: 'PUT', + url: `/review`, + payload: { + idOeuvre: 'idOeuvre', + description: 'description', + note: 5, + type: 'artist' + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(403); + }) + }) +}); diff --git a/test/unit/application/usecase/review/deleteReview.test.js b/test/unit/application/usecase/review/deleteReview.test.js new file mode 100644 index 0000000..dbf513e --- /dev/null +++ b/test/unit/application/usecase/review/deleteReview.test.js @@ -0,0 +1,50 @@ +const deleteReview = require("./../../../../../lib/application/use_cases/review/deleteReview") +const catchError = require("../utils/catchError") +describe("deleteReview Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockUserRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + } + describe("valid cases", ()=>{ + it("should delete review", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.delete = jest.fn((id) => true) + await deleteReview(1,'token', serviceLocator) + expect(mockReviewRepository.delete).toHaveBeenCalledTimes(1) + }) + }) + describe("invalid cases", ()=>{ + it("should throw error bad auth token", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => null) + const error = await catchError(async ()=>{ + await deleteReview(1,'token', serviceLocator) + }) + expect(error.code).toBe(401) + }) + it("should throw error not post owner", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.delete = jest.fn((id) => false) + const error = await catchError(async ()=>{ + await deleteReview(1,'token', serviceLocator) + }) + expect(error.code).toBe(403) + }) + + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/getReviewFixture.js b/test/unit/application/usecase/review/fixture/getReviewFixture.js new file mode 100644 index 0000000..8f6e7a9 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/getReviewFixture.js @@ -0,0 +1,120 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const mockUserPrivate = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUser +} +const expectedReview = { + id_review:1, + description: "C'est top", + countlikes: 2, + countComment: 4, + note: 5, + created_at: actualDate, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockPublicUser, + type: 'artist', +} + + + +const rawReviewPrivate = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUserPrivate +} +const expectedPrivate = { + id_review:1, + description: "C'est top", + countlikes: 2, + countComment: 4, + note: 5, + created_at: actualDate, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockUserPrivate, + type: 'artist', +} +module.exports = { + rawReview, + mockArtist, + expectedReview, + rawReviewPrivate, + expectedPrivate +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js b/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js new file mode 100644 index 0000000..c5289b5 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js @@ -0,0 +1,28 @@ +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +module.exports = { + mockUser, + mockPublicUser +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/putReviewFixture.js b/test/unit/application/usecase/review/fixture/putReviewFixture.js new file mode 100644 index 0000000..fab6827 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/putReviewFixture.js @@ -0,0 +1,88 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const mockUserPrivate = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUser +} +const expectedReview = { + id_review:1, + description: "C'est top", + countlikes: 2, + countComment: 4, + note: 5, + created_at: actualDate, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockPublicUser, + type: 'artist', +} + + + + +module.exports = { + rawReview, + mockArtist, + expectedReview +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/getReview.test.js b/test/unit/application/usecase/review/getReview.test.js new file mode 100644 index 0000000..fc98210 --- /dev/null +++ b/test/unit/application/usecase/review/getReview.test.js @@ -0,0 +1,101 @@ +const getReview = require("../../../../../lib/application/use_cases/review/getReview") +const catchError = require("../utils/catchError") + +const { + mockArtist, + rawReview, + expectedReview, + rawReviewPrivate, + expectedPrivate, +} = require("./fixture/getReviewFixture") + + +describe("getReview Test", ()=>{ + const mockReviewRepository = {} + const mockFriendRepository = {} + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + friendRepository: mockFriendRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + } + + describe("successful cases", () => { + it("should return review from repository", async () => { + mockReviewRepository.getById = jest.fn((id) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const result = await getReview(1,undefined, serviceLocator) + expect(result).toEqual(expectedReview) + }) + + it("should return review private from repository", async () => { + mockReviewRepository.getById = jest.fn((id) => rawReviewPrivate) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + const result = await getReview(1,'something', serviceLocator) + expect(result).toEqual(expectedPrivate) + }) + }) + describe("invalid cases", () => { + it("should throw review not found error", async () => { + mockReviewRepository.getById = jest.fn((id) => null) + const error = await catchError(async () => { + await getReview(1,undefined, serviceLocator) + }) + console.log(error) + expect(error.code).toBe(404) + }) + it("should throw user private 1", async () => { + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + const error = await catchError(async () => { + await getReview(1,undefined, serviceLocator) + }) + expect(error.code).toBe(403) + }) + + it("should throw user private 2", async () => { + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + const error = await catchError(async () => { + await getReview(1,'something', serviceLocator) + }) + expect(error.code).toBe(403) + expect(mockFriendRepository.areFriends).toHaveBeenCalledTimes(1) + expect().hasBee + }) + it("should throw error review not found ", async () => { + mockReviewRepository.getById = jest.fn((id) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const error = await catchError(async () => { + await getReview(1,undefined, serviceLocator) + }) + expect(error.code).toBe(400) + + }) + }) +}) diff --git a/test/unit/application/usecase/review/getReviewLikes.test.js b/test/unit/application/usecase/review/getReviewLikes.test.js new file mode 100644 index 0000000..8804049 --- /dev/null +++ b/test/unit/application/usecase/review/getReviewLikes.test.js @@ -0,0 +1,88 @@ +const getReviewLikes = require("./../../../../../lib/application/use_cases/review/getReviewLikes") +const catchError = require("../utils/catchError") +const { + mockUser, + mockPublicUser +} = require("./fixture/getReviewLikesFixture") +describe("getReviewLikes Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockUserRepository = {} + const mockFriendRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + friendRepository: mockFriendRepository + } + describe("invalid cases", ()=>{ + + it("should throw review not found error", async ()=>{ + mockReviewRepository.getById = jest.fn((id) => null) + const error = await catchError(async ()=>{ + await getReviewLikes(1,'token',1,10, serviceLocator) + }) + expect(error.code).toBe(404) + }) + it("should throw user is private error 1", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + const error = await catchError(async ()=>{ + await getReviewLikes(1,'token',1,10, serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should throw user is private error 2", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + const error = await catchError(async ()=>{ + await getReviewLikes(1,undefined,1,10, serviceLocator) + }) + expect(error.code).toBe(403) + }) + }) + describe("valid cases", ()=>{ + it("should get review private like", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: true + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) + mockReviewRepository.getLikes = jest.fn((id) => [mockUser]) + const result = await getReviewLikes(1,'token',1,10, serviceLocator) + expect(result).toEqual([mockPublicUser]) + }) + it("should get review public", async ()=>{ + const mockReview = { + utilisateur: { + id_utilisateur: 1, + pseudo: "John Doe", + is_private: false + } + } + mockReviewRepository.getById = jest.fn((id) => mockReview) + mockReviewRepository.getLikes = jest.fn((id) => [mockUser]) + const result = await getReviewLikes(1,undefined,1,10, serviceLocator) + expect(result).toEqual([mockPublicUser]) + }) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/getReviews.test.js b/test/unit/application/usecase/review/getReviews.test.js new file mode 100644 index 0000000..b4edf6d --- /dev/null +++ b/test/unit/application/usecase/review/getReviews.test.js @@ -0,0 +1,35 @@ +const getReviews = require("../../../../../lib/application/use_cases/review/getReviews") + +const { + mockArtist, + rawReview, + expectedReview, +} = require("./fixture/getReviewFixture") + +describe("getReviews Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + } + it("should return serialized review", async ()=>{ + + mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const expectedReviews = [expectedReview] + const result = await getReviews(1,10,true,undefined, serviceLocator) + expect(result).toEqual(expectedReviews) + }) + + it("should return serialized review with user login", async ()=>{ + mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const expectedReviews = [expectedReview] + const result = await getReviews(1,10,true,'something', serviceLocator) + expect(result).toEqual(expectedReviews) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js new file mode 100644 index 0000000..84d927e --- /dev/null +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -0,0 +1,86 @@ +const getUserReviews = require("../../../../../lib/application/use_cases/review/getUserReviews") +const catchError = require("../utils/catchError") +const { + mockArtist, + rawReview, + expectedReview, +} = require("./fixture/getReviewFixture") + +describe("getReviews Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const mockUserRepository = {} + const mockFriendRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + userRepository: mockUserRepository, + friendRepository: mockFriendRepository + } + describe("successful cases", ()=>{ + it("should return serialized review with public user", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: false + } + }) + mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const expectedReviews = [expectedReview] + const result = await getUserReviews(1,'token',1,10,true, serviceLocator) + expect(result).toEqual(expectedReviews) + }) + it("should return serialized review with private user", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: true + } + }) + mockAccesTokenManager.decode = jest.fn((userToken) => { + return {value: 1} + }) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) + mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const expectedReviews = [expectedReview] + const result = await getUserReviews(1,'token',1,10,true, serviceLocator) + expect(result).toEqual(expectedReviews) + }) + }) + describe("invalid cases", ()=>{ + + it("should throw error user private 1", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: true + } + }) + const error = await catchError(async ()=>{ + await getUserReviews(1,undefined,1,10,true, serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should throw error user private 2", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { + return { + id_utilisateur: 1, + is_private: true + } + }) + mockAccesTokenManager.decode = jest.fn((userToken) => { + return {value: 1} + }) + mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) + const error = await catchError(async ()=>{ + await getUserReviews(1,'token',1,10,true, serviceLocator) + }) + expect(error.code).toBe(403) + expect(mockFriendRepository.areFriends).toHaveBeenCalledTimes(1) + }) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/likeReview.test.js b/test/unit/application/usecase/review/likeReview.test.js new file mode 100644 index 0000000..a0f2097 --- /dev/null +++ b/test/unit/application/usecase/review/likeReview.test.js @@ -0,0 +1,50 @@ +const likeReview = require("../../../../../lib/application/use_cases/review/likeReview") +const catchError = require("../utils/catchError") +describe("likeReview Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockUserRepository = {} + + + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + } + describe("valid cases", ()=>{ + it("should like review", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => {return {value: 1}}) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.doesUserLikes = jest.fn((id) => true) + mockReviewRepository.unlikeReview = jest.fn((id) => true) + await likeReview(1,'token', serviceLocator) + expect(mockReviewRepository.unlikeReview).toHaveBeenCalledTimes(1) + }) + + it("should unlike review", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => {return {value: 1}}) + mockUserRepository.getByUser = jest.fn((id) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.doesUserLikes = jest.fn((id) => false) + mockReviewRepository.likeReview = jest.fn((id) => true) + await likeReview(1,'token', serviceLocator) + expect(mockReviewRepository.likeReview).toHaveBeenCalledTimes(1) + }) + }) + describe("invalid cases", ()=>{ + it("should throw error bad auth token", async ()=>{ + mockUserRepository.getByUser = jest.fn((id) => null) + const error = await catchError(async ()=>{ + await await likeReview(1,'token', serviceLocator) + }) + expect(error.code).toBe(401) + }) + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/putReview.test.js b/test/unit/application/usecase/review/putReview.test.js new file mode 100644 index 0000000..46a65aa --- /dev/null +++ b/test/unit/application/usecase/review/putReview.test.js @@ -0,0 +1,108 @@ +const putReview = require("../../../../../lib/application/use_cases/review/putReview") +const catchError = require("../utils/catchError") +describe("putReview Test", ()=>{ + const idOeuvre = 'idOeuvre' + const userToken = 'token' + const description = 'description' + const note = 5 + const type = 'artist' + const { + rawReview, + mockArtist, + expectedReview, + } = require("./fixture/putReviewFixture") + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const mockUserRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + userRepository: mockUserRepository, + } + describe("invalid cases", ()=>{ + + it("should throw error bad auth token", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => null) + const error = await catchError(async ()=>{ + await putReview(idOeuvre, userToken, description,note, type, serviceLocator) + }) + expect(error.code).toBe(401) + }) + it("should throw error review already posted", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => { + return { + id_review: 1 + } + }) + + const error = await catchError(async ()=>{ + await putReview(idOeuvre, userToken, description,note, type, serviceLocator) + }) + expect(error.code).toBe(403) + }) + it("should throw error type review doesn't exist", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => null) + mockReviewRepository.getTypeReviewID = jest.fn((type) => null) + const error = await catchError(async ()=>{ + await putReview(idOeuvre, userToken, description,note, type, serviceLocator) + }) + expect(error.code).toBe(401) + expect(mockReviewRepository.getTypeReviewID).toHaveBeenCalledTimes(1) + }) + it("should throw error rawOeuvre ", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => null) + mockReviewRepository.getTypeReviewID = jest.fn((type) => 1) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: 'error' + } + } + }) + const error = await catchError(async ()=>{ + await putReview(idOeuvre, userToken, description,note, type, serviceLocator) + }) + expect(error.code).toBe(400) + }) + }) + describe("valid cases", ()=>{ + + it("should put review", async ()=>{ + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockReviewRepository.getByUserAndId = jest.fn((idOeuvre, id_utilisateur) => null) + mockReviewRepository.getTypeReviewID = jest.fn((type) => 1) + mockReviewRepository.persist = jest.fn((reviewRaw) => rawReview) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + const result = await putReview(idOeuvre, userToken, description,note, type, serviceLocator) + expect(result).toEqual(expectedReview) + }) + + }) +}) \ No newline at end of file diff --git a/test/unit/application/usecase/spotify/getAlbum.test.js b/test/unit/application/usecase/spotify/getAlbum.test.js index 9cb9ff8..b93a8d2 100644 --- a/test/unit/application/usecase/spotify/getAlbum.test.js +++ b/test/unit/application/usecase/spotify/getAlbum.test.js @@ -13,7 +13,7 @@ const mockSpotifyRepository = {} describe('get an album usecase', () => { - it("should return an album with one artist", async ()=>{ + it("should return an album with one artist 1", async ()=>{ mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ return albumRawOneArtist }) @@ -24,7 +24,7 @@ describe('get an album usecase', () => { result.popularity = 0 expect(result).toEqual(expectedAlbumOneArtist) }) - it("should return an album with one artist", async ()=>{ + it("should return an album with one artist 2", async ()=>{ mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ return albumRawSeveralArtist }) @@ -35,7 +35,7 @@ describe('get an album usecase', () => { result.popularity = 0 expect(result).toEqual(expectedAlbumSeveralArtist) }) - it("should return an album with one artist", async ()=>{ + it("should return an album with one artist 3", async ()=>{ mockSpotifyRepository.getSpotifyAlbums = jest.fn((id) =>{ return albumRawNoArtist }) diff --git a/test/unit/interfaces/serializers/fixtures/albumFixture.js b/test/unit/interfaces/serializers/fixtures/albumFixture.js index 0c4d636..55dbab0 100644 --- a/test/unit/interfaces/serializers/fixtures/albumFixture.js +++ b/test/unit/interfaces/serializers/fixtures/albumFixture.js @@ -52,23 +52,7 @@ const expectedAlbumOneArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists:[ expectedFixture ], @@ -119,23 +103,7 @@ const expectedAlbumSeveralArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists:[ expectedFixture, expectedFixture @@ -182,23 +150,7 @@ const expectedAlbumNoArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists: undefined, tracks: undefined, genres: ["genre1", "genre2"], diff --git a/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js b/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js index 792bd82..de2b5fa 100644 --- a/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js +++ b/test/unit/interfaces/serializers/fixtures/albumTrackFixture.js @@ -118,23 +118,7 @@ const expectedAlbumOneArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists:[ expectedFixture ], @@ -192,23 +176,7 @@ const expectedAlbumSeveralArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists:[ expectedFixture, expectedFixture @@ -259,23 +227,7 @@ const expectedAlbumNoArtist = { popularity:0, release_date:"2009-02-16", spotify_url: "https://open.spotify.com/album/17UiqpQyl8T8vVxz2Towjy", - images:[ - { - url:"https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height:640, - width:640 - }, - { - url:"https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height:300, - width:300 - }, - { - url:"https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height:64, - width:64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", artists: undefined, tracks: [ expectedRawTrackWithNoArtist diff --git a/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js b/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js index f5cea4d..1cb81eb 100644 --- a/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js +++ b/test/unit/interfaces/serializers/fixtures/artistAlbumsFixture.js @@ -31,23 +31,7 @@ const artistFixture = { const artistExpectedFixture = { id: "3fMbdgg4jU18AjLCKBhRSm", name: "Michael Jackson", - images: [ - { - "url": "https://i.scdn.co/image/ab6761610000e5eb0e08ea2c4d6789fbf5cbe0aa", - "height": 640, - "width": 640 - }, - { - "url": "https://i.scdn.co/image/ab676161000051740e08ea2c4d6789fbf5cbe0aa", - "height": 320, - "width": 320 - }, - { - "url": "https://i.scdn.co/image/ab6761610000f1780e08ea2c4d6789fbf5cbe0aa", - "height": 160, - "width": 160 - } - ], + image: "https://i.scdn.co/image/ab6761610000e5eb0e08ea2c4d6789fbf5cbe0aa", popularity: 81, genres: ["r&b", "soul"], spotify_url: "https://open.spotify.com/artist/3fMbdgg4jU18AjLCKBhRSm", @@ -88,23 +72,7 @@ const artistFixtureAppearsOn = { const artistExpectedFixtureAppearsOn = { id: "3TVXtAsR1Inumwj472S9r4", name: "Drake", - images: [ - { - "url": "https://i.scdn.co/image/ab6761610000e5eb4293385d324db8558179afd9", - "height": 640, - "width": 640 - }, - { - "url": "https://i.scdn.co/image/ab676161000051744293385d324db8558179afd9", - "height": 320, - "width": 320 - }, - { - "url": "https://i.scdn.co/image/ab6761610000f1784293385d324db8558179afd9", - "height": 160, - "width": 160 - } - ], + image: "https://i.scdn.co/image/ab6761610000e5eb4293385d324db8558179afd9", popularity: 97, genres: ["canadian hip hop","canadian pop","hip hop","pop rap","rap"], spotify_url: "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4", @@ -161,23 +129,7 @@ const expectedAlbumArtist = [ popularity: 50, // rentre a la main release_date: "2022-11-18", spotify_url: "https://open.spotify.com/album/57TzZhbqvYoUBzJSVKFVlG", - images: [ - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b273822e06488f98e53e8743ff6b", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/ab67616d00001e02822e06488f98e53e8743ff6b", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/ab67616d00004851822e06488f98e53e8743ff6b", - "width": 64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b273822e06488f98e53e8743ff6b", artists: [ artistExpectedFixture ], @@ -238,23 +190,7 @@ const expectedSingleArtist = [ popularity: 50, // rentre a la main release_date: "2018-08-29", spotify_url: "https://open.spotify.com/album/6ST7naJFCe9iBeOleU5Ccu", - images: [ - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b2739e9ceac980e4b6b59a766f8b", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/ab67616d00001e029e9ceac980e4b6b59a766f8b", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/ab67616d000048519e9ceac980e4b6b59a766f8b", - "width": 64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2739e9ceac980e4b6b59a766f8b", artists: [ artistExpectedFixture ], @@ -314,23 +250,7 @@ const expectedCompilationArtist = [ popularity: 50, // rentre a la main release_date: "2017-09-27", //fait spotify_url: "https://open.spotify.com/album/2X8UOIkZQdcz2Hi5Ynt2uk", - images: [ //fait - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b273cde37cfdee48dc0eae1e2ab8", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/ab67616d00001e02cde37cfdee48dc0eae1e2ab8", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/ab67616d00004851cde37cfdee48dc0eae1e2ab8", - "width": 64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b273cde37cfdee48dc0eae1e2ab8", artists: [ artistExpectedFixture ], @@ -391,23 +311,7 @@ const expectedAppearsOnArtist = [ popularity: 50, // rentre a la main release_date: "2018-06-29", //fait spotify_url: "https://open.spotify.com/album/1ATL5GLyefJaxhQzSPVrLX", - images: [ - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b273f907de96b9a4fbc04accc0d5", - "width": 640 - }, - { - "height": 300, - "url": "https://i.scdn.co/image/ab67616d00001e02f907de96b9a4fbc04accc0d5", - "width": 300 - }, - { - "height": 64, - "url": "https://i.scdn.co/image/ab67616d00004851f907de96b9a4fbc04accc0d5", - "width": 64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b273f907de96b9a4fbc04accc0d5", artists: [ artistExpectedFixtureAppearsOn ], diff --git a/test/unit/interfaces/serializers/fixtures/artistFixture.js b/test/unit/interfaces/serializers/fixtures/artistFixture.js index ec49a4a..c2532f2 100644 --- a/test/unit/interfaces/serializers/fixtures/artistFixture.js +++ b/test/unit/interfaces/serializers/fixtures/artistFixture.js @@ -28,23 +28,7 @@ const expectedFixture = { id: "4FpJcNgOvIpSBeJgRg3OfN", name: "Orelsan", - images: [ - { - url: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", - height: 640, - width: 640 - }, - { - url: "https://i.scdn.co/image/ab67616d00001e020b2e3999b189fa2a8a6a752f", - height: 300, - width: 300 - }, - { - url: "https://i.scdn.co/image/ab67616d000048510b2e3999b189fa2a8a6a752f", - height: 64, - width: 64 - } - ], + image: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", popularity: 10, genres:['rock','funk'], spotify_url: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", From af126dcf3ed4dacfbc247b7b9e9be8b7481c5484 Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Thu, 7 Mar 2024 21:24:07 +0100 Subject: [PATCH 35/67] ajout de l'id + modification du type pour faire appraitre appears_on --- lib/interfaces/serializers/AlbumSerializer.js | 2 +- lib/interfaces/serializers/SerializeSearchItem.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index 1ec7207..fdcda4f 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -14,7 +14,7 @@ const serializeAlbum = (albumRaw) => { artists : albumRaw?.artists?.map(item => SerializeArtist(item)), tracks: tracks, genres : albumRaw?.genres, - type : albumRaw?.album_group ? albumRaw?.album_group : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) + type : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), }; return new Album(album) diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 8c6a6c1..c333e26 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -1,6 +1,7 @@ const seralizer = { 'artist': (item) => { return { + id: item.id, imageURL: item.images[0]?.url, title: item.name, subtitle: '' @@ -9,6 +10,7 @@ const seralizer = { }, 'album': (item) => { return { + id: item.id, imageURL: item.images[0]?.url, title: item.name, subtitle: item?.artists[0].name @@ -16,6 +18,7 @@ const seralizer = { }, 'track': (item) => { return { + id: item.id, imageURL: item.album.images[0]?.url, title: item.name, subtitle: item?.artists[0].name @@ -23,6 +26,7 @@ const seralizer = { }, 'single': (item) => { return { + id: item.id, imageURL: item.images[0]?.url, title: item.name, subtitle: item?.artists[0].name @@ -30,6 +34,7 @@ const seralizer = { }, 'compilation': (item) => { return { + id: item.id, imageURL: item.images[0]?.url, title: item.name, subtitle: item?.artists[0].name @@ -37,6 +42,7 @@ const seralizer = { }, 'user': (item) => { return { + id: item.id, imageURL: item.photo, title: item.alias, subtitle: item.pseudo From 379af7816129150b14114d4517540d2667bfbfaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:01:49 +0100 Subject: [PATCH 36/67] Feature/artist pagej (#37) * save * get artist/{id} v1 * get artist/{id} v2 * added field in getArtist * added album's reviews count, create_at for reviews and ids for user public * get oeuvres review with op.in * test & correction * save * get artist/{id} v1 * get artist/{id} v2 * added field in getArtist * added album's reviews count, create_at for reviews and ids for user public * get oeuvres review with op.in * test & correction * test correction --- lib/application/use_cases/artist/getArtist.js | 92 ++++ lib/application/use_cases/review/getReview.js | 15 +- .../use_cases/review/getReviews.js | 10 +- .../use_cases/review/getUserReviews.js | 14 +- lib/application/use_cases/review/putReview.js | 7 +- .../use_cases/review/util/getReview.js | 2 +- .../use_cases/spotify/FetchArtistSongs.js | 1 - lib/domain/entity/ArtistEntity.js | 9 + lib/domain/entity/ReviewEntity.js | 2 +- lib/domain/model/Album.js | 2 + lib/domain/model/Artist.js | 2 +- lib/domain/model/ArtistPage.js | 10 + lib/domain/model/Review.js | 2 +- lib/domain/model/ReviewPublic.js | 5 +- lib/domain/model/UserPublic.js | 1 + lib/infrastructure/orm/sequelize/sequelize.js | 6 +- .../repositories/FollowRepository.js | 59 ++- .../repositories/FriendRepository.js | 2 +- .../repositories/ReviewRepository.js | 81 +++- .../repositories/SpotifyRepository.js | 11 +- .../interfaces/FollowRepositoryAbstract.js | 10 +- .../interfaces/ReviewRepositoryAbstract.js | 14 + lib/infrastructure/webserver/server.js | 1 + .../controllers/ArtistController.js | 22 + lib/interfaces/routes/artist.js | 39 ++ lib/interfaces/serializers/AlbumSerializer.js | 4 +- .../serializers/ArtistSerializer.js | 1 + .../serializers/ReviewSerializer.js | 7 +- .../serializers/SerializeSearchItem.js | 3 +- scripts/insert_test_data.py | 44 ++ test/integration/artist.test.js | 123 ++++++ .../fixture/artist/getArtistFixture.js | 212 ++++++++++ test/integration/friend.test.js | 2 - test/integration/review.test.js | 1 + .../usecase/artist/getArtist.test.js | 126 ++++++ .../usecase/artist/getArtistFixture.js | 392 ++++++++++++++++++ .../usecase/fixtures/searchFixture.js | 7 + .../usecase/friend/unfollowUser.test.js | 1 - .../review/fixture/getReviewFixture.js | 22 +- .../review/fixture/getReviewLikesFixture.js | 1 + .../review/fixture/putReviewFixture.js | 8 +- .../usecase/review/getReview.test.js | 8 +- .../usecase/review/getReviews.test.js | 2 + .../usecase/review/getUserReviews.test.js | 3 +- .../usecase/review/putReview.test.js | 2 +- .../usecase/spotify/FetchArtistSongs.test.js | 1 - .../user/getUserByConfirmToken.test.js | 1 + .../usecase/user/getUserByPseudo.test.js | 1 + 48 files changed, 1332 insertions(+), 59 deletions(-) create mode 100644 lib/application/use_cases/artist/getArtist.js create mode 100644 lib/domain/entity/ArtistEntity.js create mode 100644 lib/domain/model/ArtistPage.js create mode 100644 lib/interfaces/controllers/ArtistController.js create mode 100644 lib/interfaces/routes/artist.js create mode 100644 test/integration/artist.test.js create mode 100644 test/integration/fixture/artist/getArtistFixture.js create mode 100644 test/unit/application/usecase/artist/getArtist.test.js create mode 100644 test/unit/application/usecase/artist/getArtistFixture.js diff --git a/lib/application/use_cases/artist/getArtist.js b/lib/application/use_cases/artist/getArtist.js new file mode 100644 index 0000000..47f7691 --- /dev/null +++ b/lib/application/use_cases/artist/getArtist.js @@ -0,0 +1,92 @@ +const serializeArtiste = require("../../../interfaces/serializers/ArtistSerializer"); +const throwStatusCode = require("../utils/throwStatusCode"); +const UserPublic = require("../../../domain/model/UserPublic"); +const ArtistPage = require("../../../domain/model/ArtistPage.js"); +const serializeAlbum = require("../../../interfaces/serializers/AlbumSerializer"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer.js"); +module.exports = async (artistId, userToken, {accessTokenManager,userRepository, spotifyRepository, reviewRepository,followRepository}) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + const user = await userRepository.getByUser(id_utilisateur) + if(!user) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + const artist = await spotifyRepository.getSpotifyArtist(artistId) + if(artist.error) + throwStatusCode(artist.error.status,artist.error.message) + const doesUserFollow = await followRepository.doesFollows(id_utilisateur,artistId) + const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single',50) + + const album_ids = albums.items.map((album)=>album.id) + const [ + follower_count, + friends_followers, + friend_follower_count, + reviewsByLike, + reviewsByTime, + ] = await Promise.all([ + followRepository.getFollowersCount(artistId), + followRepository.getFriendsFollowing(artistId,user.id_utilisateur,3), + followRepository.getFriendsFollowingCount(artistId,user.id_utilisateur), + reviewRepository.getOeuvreReviews(1,3,true,false,album_ids,user.id_utilisateur), + reviewRepository.getOeuvreReviews(1,3,false,false,album_ids,user.id_utilisateur), + ]) + return await artistSerizilizer( + user.id_utilisateur, + doesUserFollow, + artist, + albums, + follower_count, + friends_followers, + friend_follower_count, + reviewsByLike, + reviewsByTime, + {reviewRepository,spotifyRepository} + ) +} + +const artistSerizilizer = async ( + id_utilisateur, + doesUserFollow, + artist, + albums, + followers_count, + friends_followers, + friend_follower_count, + reviewsByLike, + reviewsByTime, + {reviewRepository,spotifyRepository}) => { + artist.follower_count = followers_count + artist = serializeArtiste(artist) + + albums = await Promise.all(albums.items.map(async (item) => { + item.rating = await reviewRepository.getOeuvreRating(item.id) + item.reviewCount = await reviewRepository.getReviewCount(item.id) + return serializeAlbum(item) + })) + + friends_followers = { + count: friend_follower_count, + users: friends_followers.map(item => { + return new UserPublic(item) + }) + } + + reviewsByLike = await Promise.all( + reviewsByLike.map(async (review) => { + const doesUserLike = await reviewRepository.doesUserLike( id_utilisateur,review.id_review) + const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLike) + }) + ) + + reviewsByTime = await Promise.all( + reviewsByTime.map(async (review) => { + const doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,review.id_review) + const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLike) + }) + ) + return new ArtistPage(artist,albums,friends_followers,reviewsByLike,reviewsByTime,doesUserFollow) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getReview.js b/lib/application/use_cases/review/getReview.js index f9e21dd..bca002c 100644 --- a/lib/application/use_cases/review/getReview.js +++ b/lib/application/use_cases/review/getReview.js @@ -1,12 +1,21 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); const getReview = require("./util/getReview") -module.exports = async (idReview,userToken, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository}) => { +module.exports = async (idReview,userToken, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,followRepository}) => { - const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository}) + const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository,followRepository}) const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) + + if(userToken) + userToken = accessTokenManager.decode(userToken)?.value + + let doesUserLike = false + if(userToken) { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) + } + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) } \ No newline at end of file diff --git a/lib/application/use_cases/review/getReviews.js b/lib/application/use_cases/review/getReviews.js index 6b93596..015a81e 100644 --- a/lib/application/use_cases/review/getReviews.js +++ b/lib/application/use_cases/review/getReviews.js @@ -5,12 +5,16 @@ module.exports = async (page,pageSize,orderByLike,userToken,{reviewRepository, s const rawReviews = await reviewRepository.getReviews(page,pageSize,orderByLike, false,userToken) const serializedReviews = [] rawReviews.forEach(element => { - serializedReviews.push(serializeReview(element,spotifyRepository)) + serializedReviews.push(serializeReview(element,userToken,spotifyRepository,reviewRepository)) }) return await Promise.all(serializedReviews) } -const serializeReview = async (rawReview,spotifyRepository) => { +const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { + let doesUserLike = false + if(id_utilisateur) { + doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) + } const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) } \ No newline at end of file diff --git a/lib/application/use_cases/review/getUserReviews.js b/lib/application/use_cases/review/getUserReviews.js index 935c479..2bee394 100644 --- a/lib/application/use_cases/review/getUserReviews.js +++ b/lib/application/use_cases/review/getUserReviews.js @@ -2,10 +2,11 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); module.exports = async (pseudo, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,userRepository}) => { const testUsers = await userRepository.getByEmailOrPseudo(pseudo,pseudo) + let id = null if (testUsers?.is_private) { let valid = false if (userToken) { - const id = accessTokenManager.decode(userToken)?.value + id = accessTokenManager.decode(userToken)?.value if (await friendRepository.areFriends(id, testUsers.id_utilisateur)) valid = true } @@ -15,13 +16,18 @@ module.exports = async (pseudo, userToken,page,pageSize, orderByLike, {reviewRep const reviews = await reviewRepository.getReviewByUserId(testUsers.id_utilisateur,page,pageSize, orderByLike) const serializedReviews = [] reviews.forEach(element => { - serializedReviews.push(serializeReview(element,spotifyRepository)) + + serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) }) return await Promise.all(serializedReviews) } -const serializeReview = async (rawReview,spotifyRepository) => { +const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { + let doesUserLike = false + if(id_utilisateur) { + doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) + } const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) } \ No newline at end of file diff --git a/lib/application/use_cases/review/putReview.js b/lib/application/use_cases/review/putReview.js index 3a3b988..fc9a769 100644 --- a/lib/application/use_cases/review/putReview.js +++ b/lib/application/use_cases/review/putReview.js @@ -1,11 +1,12 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer") -module.exports = async (idOeuvre, userToken, description, note,type, {accessTokenManager, userRepository,reviewRepository,spotifyRepository})=> { +module.exports = async (idOeuvre, userToken, description, note,type, {accessTokenManager, userRepository,reviewRepository,spotifyRepository,followRepository})=> { const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") if(await reviewRepository.getByUserAndId(idOeuvre,id_utilisateur)) throwStatusCode(403,"vous avez déjà posté une review") const id_type_review = await reviewRepository.getTypeReviewID(type) - if(!id_type_review) throwStatusCode(401,"ce type de review n'existe pas") + if(!id_type_review) throwStatusCode(404,"ce type de review n'existe pas") const rawOeuvre = await spotifyRepository.getOeuvre(idOeuvre,type) if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) @@ -17,5 +18,5 @@ module.exports = async (idOeuvre, userToken, description, note,type, {accessToke id_type_review } const review = await reviewRepository.persist(ReviewRaw) - return reviewSerializer(review,rawOeuvre,review.utilisateur) + return reviewSerializer(review,rawOeuvre,review.utilisateur,false) } \ No newline at end of file diff --git a/lib/application/use_cases/review/util/getReview.js b/lib/application/use_cases/review/util/getReview.js index 896b171..8ae971d 100644 --- a/lib/application/use_cases/review/util/getReview.js +++ b/lib/application/use_cases/review/util/getReview.js @@ -1,6 +1,6 @@ const throwStatusCode = require("../../utils/throwStatusCode"); -module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository}) => { +module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository,followRepository}) => { const rawReview = await reviewRepository.getById(idReview) if(!rawReview) throwStatusCode(404,"la review n'existe pas") diff --git a/lib/application/use_cases/spotify/FetchArtistSongs.js b/lib/application/use_cases/spotify/FetchArtistSongs.js index c880d9c..b766109 100644 --- a/lib/application/use_cases/spotify/FetchArtistSongs.js +++ b/lib/application/use_cases/spotify/FetchArtistSongs.js @@ -14,7 +14,6 @@ module.exports = async (id, filter, limit, { spotifyRepository }) => { artistSongs = await spotifyRepository.getSpotifyArtistSongs(id, filter, limit) } catch(error){ - console.log("18 erreur") throwStatusCode(400, `l'id : ${id} n'existe pas`) } diff --git a/lib/domain/entity/ArtistEntity.js b/lib/domain/entity/ArtistEntity.js new file mode 100644 index 0000000..be8dffb --- /dev/null +++ b/lib/domain/entity/ArtistEntity.js @@ -0,0 +1,9 @@ +const Joi = require('joi') + +const getArtist = Joi.object().keys({ + id: Joi.string().max(50).required() +}) + +module.exports = { + getArtist +} \ No newline at end of file diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index 15a65d6..b2f3008 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -20,7 +20,7 @@ const putReview = Joi.object().keys({ .string() .required() .custom((value, helpers) => { - const allowedValues = ["track", "album", "artist"] + const allowedValues = ["track", "album", "artist","single","compilation"] return allowedValues.includes(value) ? value : helpers.error('any.invalid') }) }) diff --git a/lib/domain/model/Album.js b/lib/domain/model/Album.js index 92a3cc0..f30fa3c 100644 --- a/lib/domain/model/Album.js +++ b/lib/domain/model/Album.js @@ -3,6 +3,8 @@ module.exports = class { this.id = album.id this.name = album.name this.popularity = album.popularity + this.rating = album.rating + this.reviewCount = album.reviewCount this.release_date = album.release_date this.total_tracks = album.total_tracks this.image = album.image diff --git a/lib/domain/model/Artist.js b/lib/domain/model/Artist.js index 46254fa..ed67f9f 100644 --- a/lib/domain/model/Artist.js +++ b/lib/domain/model/Artist.js @@ -1,10 +1,10 @@ -const {spotify_url} = require("./Track"); module.exports = class { constructor(artist) { this.id = artist?.id; this.name = artist.name; this.image = artist?.image; this.popularity = artist?.popularity + this.follower_count = artist?.follower_count this.genres = artist?.genres this.spotify_url = artist?.spotify_url this.type = "artist" diff --git a/lib/domain/model/ArtistPage.js b/lib/domain/model/ArtistPage.js new file mode 100644 index 0000000..5756d84 --- /dev/null +++ b/lib/domain/model/ArtistPage.js @@ -0,0 +1,10 @@ +module.exports = class { + constructor(artist,albums,friends_followers,reviewsByLike,reviewsByTime,doesUserFollow){ + this.artist = artist + this.albums = albums + this.friends_followers = friends_followers + this.reviewsByLike = reviewsByLike + this.reviewsByTime = reviewsByTime + this.doesUserFollow = doesUserFollow + } +} \ No newline at end of file diff --git a/lib/domain/model/Review.js b/lib/domain/model/Review.js index a7746c1..dd1712f 100644 --- a/lib/domain/model/Review.js +++ b/lib/domain/model/Review.js @@ -7,7 +7,7 @@ module.exports = class { this.countComment = rawReview.dataValues.countComment this.description = rawReview.description this.note = rawReview.note - this.created_at = rawReview.createdAt + this.createAt = rawReview.createdAt this.updated_at = rawReview.updatedAt this.type = type diff --git a/lib/domain/model/ReviewPublic.js b/lib/domain/model/ReviewPublic.js index 4a7bf8d..693323e 100644 --- a/lib/domain/model/ReviewPublic.js +++ b/lib/domain/model/ReviewPublic.js @@ -1,11 +1,12 @@ module.exports = class { - constructor(rawReview,oeuvre,utilisateur) { + constructor(rawReview,oeuvre,utilisateur,doesUserLike) { this.id_review = rawReview.id_review this.description = rawReview.description this.countlikes = rawReview.countlikes this.countComment = rawReview.countComment + this.doesUserLike = doesUserLike this.note = rawReview.note - this.created_at = rawReview.createdAt + this.createAt = rawReview.created_at this.oeuvre = oeuvre this.utilisateur = utilisateur this.type = rawReview.type diff --git a/lib/domain/model/UserPublic.js b/lib/domain/model/UserPublic.js index b513a63..a3c3ce9 100644 --- a/lib/domain/model/UserPublic.js +++ b/lib/domain/model/UserPublic.js @@ -3,6 +3,7 @@ module.exports = class { constructor(userRaw) { + this.id_utilisateur = userRaw?.id_utilisateur this.pseudo = userRaw?.pseudo; this.email = userRaw?.email; this.alias = userRaw?.alias diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index b77fa91..d6d365a 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -54,7 +54,7 @@ CommentaireModel.belongsToMany(UserModel, UserModel.belongsToMany(ArtisteModel, { - as: 'utilisateur', + as: 'utilisateur_m_n', foreignKey: 'id_utilisateur', through : 'follow' } @@ -129,7 +129,9 @@ TypeReviewModel.sync() return TypeReviewModel.bulkCreate([ {id_type_review: 1, libelle : 'track'}, {id_type_review: 2, libelle : 'album'}, - {id_type_review: 3, libelle : 'artist'} + {id_type_review: 3, libelle : 'artist'}, + {id_type_review: 4, libelle : 'single'}, + {id_type_review: 4, libelle : 'compilation'}, ]) }) .then(() => console.log('Creation des types de reviews réussi')) diff --git a/lib/infrastructure/repositories/FollowRepository.js b/lib/infrastructure/repositories/FollowRepository.js index 52ece83..29f8f59 100644 --- a/lib/infrastructure/repositories/FollowRepository.js +++ b/lib/infrastructure/repositories/FollowRepository.js @@ -1,9 +1,10 @@ 'use strict'; const sequelize = require('../orm/sequelize/sequelize'); -const user = require('../../domain/model/User'); const FollowRepository = require('./interfaces/FollowRepositoryAbstract'); -const { Op} = require('sequelize'); +const User = require("../../domain/model/User"); +const {Op} = require('sequelize'); + module.exports = class extends FollowRepository { constructor() { @@ -11,6 +12,7 @@ module.exports = class extends FollowRepository { this.db = sequelize; this.followModel = this.db.model('follow'); this.artistModel = this.db.model('artiste'); + this.userModel = this.db.model('utilisateur'); } async follow(userId, artistId) { @@ -60,5 +62,58 @@ module.exports = class extends FollowRepository { }) return !!follow } + + async getFollowersCount(idArtist) { + const artist = await this.artistModel.findOne({ + where: { + id_artiste: idArtist + } + }) + return artist?.nb_suivis || 0 + } + + async getFriendsFollowing(idArtist,idUser,limit) { + const users = await this.userModel.findAll({ + limit, + include: [ + { + model: this.artistModel, + as: 'utilisateur_m_n', + required: true, + through: { + where: { + id_artiste: idArtist + } + } + } + ], + where: { + id_utilisateur: {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser}))`)} + } + }) + + return users.map(item => new User(item)) + } + async getFriendsFollowingCount(idArtist,idUser) { + const countUsers = await this.userModel.count({ + include: [ + { + model: this.artistModel, + as: 'utilisateur_m_n', + required: true, + through: { + where: { + id_artiste: idArtist + } + } + } + ], + where: { + id_utilisateur: {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = :idUser))`)} + }, + replacements: { idUser }, + }) + return countUsers + } }; diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index 9068bcf..598a5cf 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -18,7 +18,6 @@ module.exports = class extends FriendRepositoryAbstract { async persist(friendEntity) { friendEntity.createdAt = sequelize.literal('CURRENT_TIMESTAMP'); friendEntity.updatedAt = sequelize.literal('CURRENT_TIMESTAMP'); - console.log(friendEntity); const user = await this.model.create(friendEntity); user.save(); return this.createAmis(user); @@ -97,6 +96,7 @@ module.exports = class extends FriendRepositoryAbstract { } async areFriends(id, amiIdUtilisateur) { + if(id === amiIdUtilisateur) return true const count = await this.model.count({ where: { [Op.or]: [ diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js index 8fdc876..ffaf995 100644 --- a/lib/infrastructure/repositories/ReviewRepository.js +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -2,7 +2,7 @@ const sequelize = require('../orm/sequelize/sequelize'); const ReviewRepository = require('./interfaces/ReviewRepositoryAbstract'); -const { Op} = require('sequelize'); +const {Op} = require('sequelize'); const User = require("../../domain/model/User"); const Review = require("../../domain/model/Review"); module.exports = class extends ReviewRepository { @@ -104,7 +104,51 @@ module.exports = class extends ReviewRepository { let whereClause = {} if(!fetchPrivate) { whereClause.id_utilisateur = id - ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} + ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} + : {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false`)} + } + + const reviews = await this.review.findAll({ + limit: pageSize, + offset: offset, + attributes: { + include: [ + [ + sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), + 'countLike' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), + 'countComment' + ], + ] + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review" + } + ], + order, + where: whereClause, + }) + return reviews.map(item => this.createReview(item)) + } + + + async getOeuvreReviews(page,pageSize,orderByLike, fetchPrivate,ids_oeuvre, id) { + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + let whereClause = {id_oeuvre: {[Op.in]: ids_oeuvre}} + if(!fetchPrivate) { + whereClause.id_utilisateur = id + ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} } @@ -168,7 +212,7 @@ module.exports = class extends ReviewRepository { const whereClause = {} whereClause['$review_like->like_review.id_review$'] = id_review whereClause.id_utilisateur = id_utilisateur - ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))`)} + ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))`)} : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} const reviews = await this.review.findAll({ offset: offset, @@ -221,8 +265,37 @@ module.exports = class extends ReviewRepository { }, order }) - console.log(reviews) return reviews.map(item => this.createReview(item)) } + + async getOeuvreRating(id_oeuvre) { + const seqReview = await this.review.findOne({ + where: { + id_oeuvre + }, + attributes: [ + [sequelize.fn('AVG', sequelize.col('note')), 'rating'] + ], + }); + return seqReview.dataValues.rating + } + + async doesUserLike(id_utilisateur,id_review) { + return !!(await this.likeReviewModel.findOne({ + where: { + id_utilisateur, + id_review + }, + })) + + } + + async getReviewCount(id_oeuvre) { + return await this.review.count({ + where: { + id_oeuvre + }, + }) + } }; diff --git a/lib/infrastructure/repositories/SpotifyRepository.js b/lib/infrastructure/repositories/SpotifyRepository.js index b56f7bc..239b4e1 100644 --- a/lib/infrastructure/repositories/SpotifyRepository.js +++ b/lib/infrastructure/repositories/SpotifyRepository.js @@ -44,9 +44,12 @@ module.exports = class extends spotifyRepositoryAbstract{ async getSpotifyArtist(id){ return axios.get(`https://api.spotify.com/v1/artists/${id}`, { - headers: { - 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, - }, + validateStatus: function (status) { + return true; + }, + headers: { + 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, + }, }) .then((response) => { return response.data @@ -150,7 +153,7 @@ module.exports = class extends spotifyRepositoryAbstract{ 'Authorization': `Bearer ${await this.getSpotifyAccessToken()}`, }, params: { - album_type: filter, // a gauche le champs de l'API Spotify + include_groups: filter, // N'APPARAIT PAS DANS LES PARAMS DE LA REQUETE https://developer.spotify.com/documentation/web-api/reference/get-an-artists-albums limit: limit }, }) diff --git a/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js index 9f26e7f..7ad4b98 100644 --- a/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/FollowRepositoryAbstract.js @@ -12,6 +12,14 @@ module.exports = class { doesFollows(userId, artistId) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + getFollowersCount(idArtist) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getFriendsFollowing(idArtist,idUser,limit) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } - + async getFriendsFollowingCount(idArtist,idUser) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js index 6baf51d..ec03b20 100644 --- a/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/ReviewRepositoryAbstract.js @@ -38,4 +38,18 @@ module.exports = class { getReviewByUserId(id_utilisateur,pageSize,orderByLike) { throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); } + + getOeuvreReviews(page,pageSize,orderByLike, fetchPrivate, ids_oeuvre,id) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + doesUserLike(id_utilisateur,id_review) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getOeuvreRating(id_oeuvre) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + getReviewCount(id_oeuvre) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } }; diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 3f61be7..500a854 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -86,6 +86,7 @@ const createServer = async () => { require('../../interfaces/routes/review'), require('../../interfaces/routes/upload'), require('../../interfaces/routes/friends'), + require('../../interfaces/routes/artist'), ]); refreshTokens(serviceLocator) removeExpiredConfirmTokens(serviceLocator) diff --git a/lib/interfaces/controllers/ArtistController.js b/lib/interfaces/controllers/ArtistController.js new file mode 100644 index 0000000..dc26fdb --- /dev/null +++ b/lib/interfaces/controllers/ArtistController.js @@ -0,0 +1,22 @@ +'use strict'; + +const getArtist = require('../../application/use_cases/artist/getArtist'); +const handleError = require("./utils/handleError"); + +module.exports = { + + async get(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {id} = request.params + try{ + return handler.response(await getArtist(id,token, serviceLocator)).code(200) + } + catch (e){ + return handleError(e) + } + }, + + +} diff --git a/lib/interfaces/routes/artist.js b/lib/interfaces/routes/artist.js new file mode 100644 index 0000000..c066162 --- /dev/null +++ b/lib/interfaces/routes/artist.js @@ -0,0 +1,39 @@ +const ArtistController = require("../controllers/ArtistController"); +const { + getArtist +} = require("../../domain/entity/ArtistEntity"); +module.exports = { + name: 'artist', + version: '1.0.0', + register: async (server) => { + server.route([ + { + method: 'GET', + path: '/artist/{id}', + handler: ArtistController.get, + options: { + auth: 'jwt', + description: 'get artist information', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: getArtist + } + }, + }, + ]); + } +}; \ No newline at end of file diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index 002a07e..a1686e7 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -14,7 +14,9 @@ const serializeAlbum = (albumRaw) => { artists : albumRaw?.artists?.map(item => SerializeArtist(item)), tracks: tracks, genres : albumRaw?.genres, - type : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) + type : albumRaw?.album_group ? albumRaw?.album_group : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) + rating: albumRaw?.rating, + reviewCount: albumRaw?.reviewCount, popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), }; return new Album(album) diff --git a/lib/interfaces/serializers/ArtistSerializer.js b/lib/interfaces/serializers/ArtistSerializer.js index 8d59230..249b1ac 100644 --- a/lib/interfaces/serializers/ArtistSerializer.js +++ b/lib/interfaces/serializers/ArtistSerializer.js @@ -3,6 +3,7 @@ const serializeArtiste = (artisteRaw) => { const artist = { id: artisteRaw.id, name: artisteRaw.name, + follower_count: artisteRaw?.follower_count, image: artisteRaw?.images ? artisteRaw.images[0].url : null, spotify_url : artisteRaw?.external_urls?.spotify, popularity: artisteRaw?.popularity, diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js index 99205f1..3ebac43 100644 --- a/lib/interfaces/serializers/ReviewSerializer.js +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -4,7 +4,7 @@ const trackSerializer = require("./TrackSerializer"); const albumSerializer = require("./AlbumSerializer"); const UserPublic = require("../../domain/model/UserPublic") const ReviewPublic = require("../../domain/model/ReviewPublic"); -const serializeReview = (rawReview,rawOeuvre,utilisateur) => { +const serializeReview = (rawReview,rawOeuvre,utilisateur,doesUserLike) => { switch (rawReview.type){ case 'artist': rawOeuvre = artistSerializer(rawOeuvre) @@ -15,10 +15,13 @@ const serializeReview = (rawReview,rawOeuvre,utilisateur) => { case 'album': rawOeuvre = albumSerializer(rawOeuvre) break + case 'single': + rawOeuvre = albumSerializer(rawOeuvre) + break default: rawOeuvre = null } - return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur)) + return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur),doesUserLike) } module.exports = serializeReview \ No newline at end of file diff --git a/lib/interfaces/serializers/SerializeSearchItem.js b/lib/interfaces/serializers/SerializeSearchItem.js index 188723c..96349f1 100644 --- a/lib/interfaces/serializers/SerializeSearchItem.js +++ b/lib/interfaces/serializers/SerializeSearchItem.js @@ -19,7 +19,6 @@ const seralizer = { 'track': (item) => { return { id: item.id, - imageURL: item.album.images[0]?.url, imageURL: item.album.image, title: item.name, subtitle: item?.artists[0].name @@ -44,7 +43,7 @@ const seralizer = { }, 'user': (item) => { return { - id: item.id, + id: item.id_utilisateur, imageURL: item.photo, title: item.alias, subtitle: item.pseudo diff --git a/scripts/insert_test_data.py b/scripts/insert_test_data.py index d07ddb1..cfd9690 100644 --- a/scripts/insert_test_data.py +++ b/scripts/insert_test_data.py @@ -11,6 +11,9 @@ def getRandomImageURL(): file = f"00{folder}{'000'[len(str(val)):] + str(val)}" return f"{baseUrl}/0/{folder}/{file}.jpg" + + + def insertUser(cursor,pseudo,password,bio): try: @@ -44,6 +47,35 @@ def insertUser(cursor,pseudo,password,bio): print(f"Erreur lors de l'insertion dans la base de données : {err}") return user_id + +def insert_follow(cursor,ids,artists): + date = faker.Faker().date_between(start_date='-1y', end_date='today') + for artist in artists: + data_to_insert = { + "createdAt": date, + "updatedAt": date, + "nb_suivis": 0, + "id_artiste": artist, + } + insert_reponse = """ + INSERT INTO `artiste`(`nb_suivis`,`id_artiste`, `createdAt`, `updatedAt` ) + VALUES (%(nb_suivis)s,%(id_artiste)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_reponse, data_to_insert) + for artist in artists: + for id_utilisateur in ids: + if(random.random() > 0.85): continue + data_to_insert = { + "createdAt": date, + "updatedAt": date, + "id_utilisateur": id_utilisateur, + "id_artiste": artist, + } + insert_reponse = """ + INSERT INTO `follow`(`id_utilisateur`,`id_artiste`, `createdAt`, `updatedAt` ) + VALUES (%(id_utilisateur)s,%(id_artiste)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_reponse, data_to_insert) def putReponse(cursor,reponse,ids, review_ids): reponse_ids = [] for i in range(len(review_ids)): @@ -246,6 +278,15 @@ def obtenir_prenoms_aleatoires(nombre): "5LI7PoHEolR8plrf3I16sq", "5H6Jp0syB5yEPk7SWYdlmk", ] + +artists = [ + '6mEQK9m2krja6X1cfsAjfl', + '7yeFMUrYTY5cAZx0GKXnti', + '7Ln80lUS6He07XvHI8qqHH', + '13ubrt8QOOCPljQ2FL1Kca', + '711MCceyCBcFnzjGY4Q7Un', + '3QVolfxko2UyCOtexhVTli' +] pseudo = obtenir_prenoms_aleatoires(n) conn = mysql.connector.connect( host="localhost", @@ -263,6 +304,9 @@ def obtenir_prenoms_aleatoires(nombre): id = insertUser(cursor,data,password,random.choice(biographies)) if(id is not None): ids.append(id) + +print("-------------INSERT FOLLOW----------------------") +insert_follow(cursor,ids,artists) print("-------------INSERT RELATIONS----------------------") insert_relation(cursor,ids) diff --git a/test/integration/artist.test.js b/test/integration/artist.test.js new file mode 100644 index 0000000..2e9286a --- /dev/null +++ b/test/integration/artist.test.js @@ -0,0 +1,123 @@ +'use strict'; +const Hapi = require('@hapi/hapi'); +const strategy = require("../../lib/infrastructure/config/strategy"); +const Jwt = require("@hapi/jwt"); +const jwt = require('jsonwebtoken'); + +require('dotenv').config() +let server +const mockReviewRepository = {} +const mockAccesTokenManager = {} +const mockUserRepository = {} +const mockSpotifyRepository = {} +const mockFollowRepository = {} + +mockAccesTokenManager.generate = ((test) =>{return ''}) +const mockToken = jwt.sign({ + sub: 'my-sub', + value: 1, + aud: 'urn:audience:test', + iss: 'urn:issuer:test', + expiresIn: '365d' +}, process.env.SECRET_ENCODER) + +describe('artist route', () => { + + beforeEach(async () => { + + server = Hapi.server({ + port: process.env.PORT || 3000 + }); + server.app.serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + spotifyRepository: mockSpotifyRepository, + followRepository: mockFollowRepository, + } + server.register(Jwt) + server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + await server.register([ + require('../../lib/interfaces/routes/artist'), + ]); + }); + + afterEach(async () => { + jest.clearAllMocks(); + await server.stop(); + }); + const { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, + } = require('./fixture/artist/getArtistFixture') + describe("GET review route", ()=>{ + + it("should return status code 200", async () => { + + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) + const res1 = await server.inject({ + method: 'GET', + url: `/artist/test`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(200); + + }) + it("should return status code 401", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => null) + const res1 = await server.inject({ + method: 'GET', + url: `/artist/test`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(401); + + }) + it("should return status code 400", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const res1 = await server.inject({ + method: 'GET', + url: `/artist/test`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(400); + + }) + + }) + + +}); diff --git a/test/integration/fixture/artist/getArtistFixture.js b/test/integration/fixture/artist/getArtistFixture.js new file mode 100644 index 0000000..6a8aac6 --- /dev/null +++ b/test/integration/fixture/artist/getArtistFixture.js @@ -0,0 +1,212 @@ +const mockArtist = { + external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, + genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], + id: '4FpJcNgOvIpSBeJgRg3OfN', + images: [ + { + height: 640, + url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', + width: 640 + }, + ], + name: 'Orelsan', + popularity: 64, + type: 'artist', + } +const mockAlbumRaw = { + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", + "items": [ + { + "album_group": "album", + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "release_date": "2022-10-28", + "total_tracks": 25, + "type": "album" + } + ] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const mockUserPrivate = { + pseudo: "John Doe2", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() + + +const mockLikedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockCommentedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockOeuvreReviewSpotify = { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "genres": [], + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "tracks": { + "href": "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", + "items": [ + { + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist", + "uri": "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN" + } + ], + + "disc_number": 1, + "duration_ms": 161732, + "external_urls": { + "spotify": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4" + }, + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "preview_url": "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", + "track_number": 1, + "type": "track" + } + ], + "total": 25 + }, + "type": "album" +} + + +module.exports = { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, +} \ No newline at end of file diff --git a/test/integration/friend.test.js b/test/integration/friend.test.js index 3709383..51d19f8 100644 --- a/test/integration/friend.test.js +++ b/test/integration/friend.test.js @@ -288,7 +288,6 @@ describe('friend route', () => { Authorization: `Bearer ${mockToken}` } }); - console.log(res) expect(res.statusCode).toBe(400); expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(3) expect(mockFriendRepository.getById).toHaveBeenCalledTimes(0) @@ -308,7 +307,6 @@ describe('friend route', () => { Authorization: `Bearer ${mockToken}` } }); - console.log(res) expect(res.statusCode).toBe(200); expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(3) expect(mockFriendRepository.getById).toHaveBeenCalledTimes(1) diff --git a/test/integration/review.test.js b/test/integration/review.test.js index 2449820..75b29e6 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -130,6 +130,7 @@ describe('review route', () => { it("should return status code 200 with user login", async ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) const res1 = await server.inject({ method: 'GET', diff --git a/test/unit/application/usecase/artist/getArtist.test.js b/test/unit/application/usecase/artist/getArtist.test.js new file mode 100644 index 0000000..b886d92 --- /dev/null +++ b/test/unit/application/usecase/artist/getArtist.test.js @@ -0,0 +1,126 @@ +const getArtist = require("./../../../../../lib/application/use_cases/artist/getArtist") +const catchError = require("../utils/catchError") +const { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, + expectedArtist +} = require('./getArtistFixture') +describe("getArtist Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockUserRepository = {} + const mockSpotifyRepository = {} + const mockFollowRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + spotifyRepository: mockSpotifyRepository, + followRepository: mockFollowRepository, + } + describe("valid cases", ()=>{ + it("should return valid artist", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) + const result = await getArtist(1,'token', serviceLocator) + result.albums[0].popularity = 64 + expect(result).toEqual(expectedArtist) + }) + }) + describe("invalid cases", ()=>{ + it("should throw error bad auth token", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => null) + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(401) + }) + it("should throw error artist not found", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + it("should throw error oeuvre not found 1", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ + error: { + status: 400, + message: "message", + } + }) + + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + it("should throw error oeuvre not found 2", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ + error: { + status: 400, + message: "message", + } + }) + + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/artist/getArtistFixture.js b/test/unit/application/usecase/artist/getArtistFixture.js new file mode 100644 index 0000000..4ef1190 --- /dev/null +++ b/test/unit/application/usecase/artist/getArtistFixture.js @@ -0,0 +1,392 @@ +const mockArtist = { + external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, + genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], + id: '4FpJcNgOvIpSBeJgRg3OfN', + images: [ + { + height: 640, + url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', + width: 640 + }, + ], + name: 'Orelsan', + popularity: 64, + type: 'artist', + } +const mockAlbumRaw = { + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", + "items": [ + { + "album_group": "album", + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "release_date": "2022-10-28", + "total_tracks": 25, + "type": "album" + } + ] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const mockUserPrivate = { + pseudo: "John Doe2", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() + + +const mockLikedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockCommentedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockOeuvreReviewSpotify = { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "genres": [], + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "tracks": { + "href": "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", + "items": [ + { + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist", + "uri": "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN" + } + ], + + "disc_number": 1, + "duration_ms": 161732, + "external_urls": { + "spotify": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4" + }, + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "preview_url": "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", + "track_number": 1, + "type": "track" + } + ], + "total": 25 + }, + "type": "album" +} + + +const expectedArtist = { + "artist": { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", + "popularity": 64, + "follower_count": 100, + "genres": [ + "french hip hop", + "old school rap francais", + "rap conscient" + ], + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + }, + "albums": [ + { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 64, + "rating": 1, + "reviewCount": 2, + "release_date": "2022-10-28", + "total_tracks": 25, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "type": "album" + } + ], + "friends_followers": { + "count": 1, + "users": [ + { + "id_utilisateur": 1, + "pseudo": "John Doe2", + "email": "testemail@gmail", + "alias": "John", + "photo": null, + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + } + ] + }, + "reviewsByLike": [ + { + "id_review": 25, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "countlikes": 32, + "countComment": 0, + "doesUserLike": true, + "note": 5, + "oeuvre": { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "tracks": [ + { + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "duration_ms": 161732, + "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + "type": "track" + } + ], + "genres": [], + "type": "album" + }, + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + }, + "type": "album" + } + ], + "reviewsByTime": [ + { + "id_review": 25, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "countlikes": 32, + "countComment": 0, + "doesUserLike": true, + "note": 5, + "oeuvre": { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "tracks": [ + { + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "duration_ms": 161732, + "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + "type": "track" + } + ], + "genres": [], + "type": "album" + }, + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + }, + "type": "album" + } + ], + "doesUserFollow": true +} +module.exports = { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, + expectedArtist +} \ No newline at end of file diff --git a/test/unit/application/usecase/fixtures/searchFixture.js b/test/unit/application/usecase/fixtures/searchFixture.js index 25a01fe..4e0a72f 100644 --- a/test/unit/application/usecase/fixtures/searchFixture.js +++ b/test/unit/application/usecase/fixtures/searchFixture.js @@ -28,12 +28,14 @@ const SpotifyRepositoryFixture = { } const expectedSearchResult = [ { + id: "4FpJcNgOvIpSBeJgRg3OfN", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "", title: "Orelsan", type: "artist" }, { + id: "17UiqpQyl8T8vVxz2Towjy", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "Orelsan", title: "Perdu D'Avance", @@ -41,6 +43,7 @@ const expectedSearchResult = [ }, { + id: "test_id", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "Orelsan", title: "test_name", @@ -49,12 +52,14 @@ const expectedSearchResult = [ ] const expectedSearchResultWithUsers = [ { + id: "4FpJcNgOvIpSBeJgRg3OfN", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "", title: "Orelsan", type: "artist" }, { + id: "17UiqpQyl8T8vVxz2Towjy", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "Orelsan", title: "Perdu D'Avance", @@ -62,12 +67,14 @@ const expectedSearchResultWithUsers = [ }, { + id: "test_id", imageURL: "https://i.scdn.co/image/ab67616d0000b2730b2e3999b189fa2a8a6a752f", subtitle: "Orelsan", title: "test_name", type: "track" }, { + id: "id_utilisateur", imageURL: "photo", subtitle: "pseudo", title: "alias", diff --git a/test/unit/application/usecase/friend/unfollowUser.test.js b/test/unit/application/usecase/friend/unfollowUser.test.js index 8fc7092..3d2fd0d 100644 --- a/test/unit/application/usecase/friend/unfollowUser.test.js +++ b/test/unit/application/usecase/friend/unfollowUser.test.js @@ -69,7 +69,6 @@ describe("unfollowUser", ()=>{ const error = await catchError(async ()=>{ await unfollowUser("testtoken",-2,serviceLocator) }) - console.log(error) expect(error.code).toBe(400) expect(mockAccesTokenManager.decode).toHaveBeenCalledTimes(1) expect(mockUserRepository.getByUser).toHaveBeenCalledTimes(1) diff --git a/test/unit/application/usecase/review/fixture/getReviewFixture.js b/test/unit/application/usecase/review/fixture/getReviewFixture.js index 8f6e7a9..fc02ab2 100644 --- a/test/unit/application/usecase/review/fixture/getReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/getReviewFixture.js @@ -21,13 +21,13 @@ const mockUser = { photo_temporaire: null, type: "user", is_private: false - } const mockPublicUser = { pseudo: "John Doe", alias: "John", ban_until: null, email: "testemail@gmail", + id_utilisateur: 1, id_role: 1, photo: null, photo_temporaire: null, @@ -40,6 +40,7 @@ const mockUserPrivate = { ban_until: null, email: "testemail@gmail", id_role: 1, + id_utilisateur: 1, photo: null, photo_temporaire: null, type: "user", @@ -53,8 +54,8 @@ const rawReview = { countComment: 4, description: "C'est top", note: 5, - createdAt: actualDate, - updatedAt: actualDate, + created_at: actualDate, + updated_at: actualDate, type: 'artist', utilisateur: mockUser } @@ -64,7 +65,8 @@ const expectedReview = { countlikes: 2, countComment: 4, note: 5, - created_at: actualDate, + createAt: actualDate, + doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", name: "Bob Marley", @@ -87,8 +89,8 @@ const rawReviewPrivate = { countComment: 4, description: "C'est top", note: 5, - createdAt: actualDate, - updatedAt: actualDate, + created_at: actualDate, + updated_at: actualDate, type: 'artist', utilisateur: mockUserPrivate } @@ -98,7 +100,8 @@ const expectedPrivate = { countlikes: 2, countComment: 4, note: 5, - created_at: actualDate, + createAt: actualDate, + doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", name: "Bob Marley", @@ -111,10 +114,13 @@ const expectedPrivate = { utilisateur: mockUserPrivate, type: 'artist', } + + module.exports = { rawReview, mockArtist, expectedReview, rawReviewPrivate, - expectedPrivate + expectedPrivate, + } \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js b/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js index c5289b5..a3566da 100644 --- a/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js +++ b/test/unit/application/usecase/review/fixture/getReviewLikesFixture.js @@ -15,6 +15,7 @@ const mockPublicUser = { pseudo: "John Doe", alias: "John", ban_until: null, + id_utilisateur: 1, email: "testemail@gmail", id_role: 1, photo: null, diff --git a/test/unit/application/usecase/review/fixture/putReviewFixture.js b/test/unit/application/usecase/review/fixture/putReviewFixture.js index fab6827..7483044 100644 --- a/test/unit/application/usecase/review/fixture/putReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/putReviewFixture.js @@ -25,6 +25,7 @@ const mockUser = { } const mockPublicUser = { pseudo: "John Doe", + id_utilisateur:1, alias: "John", ban_until: null, email: "testemail@gmail", @@ -53,8 +54,8 @@ const rawReview = { countComment: 4, description: "C'est top", note: 5, - createdAt: actualDate, - updatedAt: actualDate, + created_at: actualDate, + updated_at: actualDate, type: 'artist', utilisateur: mockUser } @@ -64,7 +65,8 @@ const expectedReview = { countlikes: 2, countComment: 4, note: 5, - created_at: actualDate, + createAt: actualDate, + doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", name: "Bob Marley", diff --git a/test/unit/application/usecase/review/getReview.test.js b/test/unit/application/usecase/review/getReview.test.js index fc98210..dd15520 100644 --- a/test/unit/application/usecase/review/getReview.test.js +++ b/test/unit/application/usecase/review/getReview.test.js @@ -23,21 +23,23 @@ describe("getReview Test", ()=>{ } describe("successful cases", () => { - it("should return review from repository", async () => { + it("should return review with artist from repository", async () => { mockReviewRepository.getById = jest.fn((id) => rawReview) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) const result = await getReview(1,undefined, serviceLocator) + console.log(result) expect(result).toEqual(expectedReview) }) - - it("should return review private from repository", async () => { + it("should return review with artist private from repository", async () => { mockReviewRepository.getById = jest.fn((id) => rawReviewPrivate) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) const result = await getReview(1,'something', serviceLocator) expect(result).toEqual(expectedPrivate) }) + }) describe("invalid cases", () => { it("should throw review not found error", async () => { diff --git a/test/unit/application/usecase/review/getReviews.test.js b/test/unit/application/usecase/review/getReviews.test.js index b4edf6d..8285fee 100644 --- a/test/unit/application/usecase/review/getReviews.test.js +++ b/test/unit/application/usecase/review/getReviews.test.js @@ -19,6 +19,7 @@ describe("getReviews Test", ()=>{ mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,undefined, serviceLocator) expect(result).toEqual(expectedReviews) @@ -28,6 +29,7 @@ describe("getReviews Test", ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,'something', serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js index 84d927e..b191e6b 100644 --- a/test/unit/application/usecase/review/getUserReviews.test.js +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -17,7 +17,7 @@ describe("getReviews Test", ()=>{ accessTokenManager: mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, userRepository: mockUserRepository, - friendRepository: mockFriendRepository + friendRepository: mockFriendRepository, } describe("successful cases", ()=>{ it("should return serialized review with public user", async ()=>{ @@ -46,6 +46,7 @@ describe("getReviews Test", ()=>{ mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getUserReviews(1,'token',1,10,true, serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/putReview.test.js b/test/unit/application/usecase/review/putReview.test.js index 46a65aa..a24fd26 100644 --- a/test/unit/application/usecase/review/putReview.test.js +++ b/test/unit/application/usecase/review/putReview.test.js @@ -61,7 +61,7 @@ describe("putReview Test", ()=>{ const error = await catchError(async ()=>{ await putReview(idOeuvre, userToken, description,note, type, serviceLocator) }) - expect(error.code).toBe(401) + expect(error.code).toBe(404) expect(mockReviewRepository.getTypeReviewID).toHaveBeenCalledTimes(1) }) it("should throw error rawOeuvre ", async ()=>{ diff --git a/test/unit/application/usecase/spotify/FetchArtistSongs.test.js b/test/unit/application/usecase/spotify/FetchArtistSongs.test.js index 16d1f1a..f642d82 100644 --- a/test/unit/application/usecase/spotify/FetchArtistSongs.test.js +++ b/test/unit/application/usecase/spotify/FetchArtistSongs.test.js @@ -133,7 +133,6 @@ describe('FetchArtistSongs usecase', () => { expect(mockSpotifyRepository.getSpotifyArtistSongs).toHaveBeenCalledWith(id,"album,single" , 2) }) -console.log("ONE PIECE") it("should return throw an error 400", async ()=>{ mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((id, filter, limit) =>{ throw new Error('test error') diff --git a/test/unit/application/usecase/user/getUserByConfirmToken.test.js b/test/unit/application/usecase/user/getUserByConfirmToken.test.js index a28a978..fbe79b2 100644 --- a/test/unit/application/usecase/user/getUserByConfirmToken.test.js +++ b/test/unit/application/usecase/user/getUserByConfirmToken.test.js @@ -16,6 +16,7 @@ const mockUser = { type:'user', } const expectedUser = { + id_utilisateur:1, pseudo:'pseudo', alias:'alias', email:'testemail@gmail.com', diff --git a/test/unit/application/usecase/user/getUserByPseudo.test.js b/test/unit/application/usecase/user/getUserByPseudo.test.js index 4da6124..e77b1cf 100644 --- a/test/unit/application/usecase/user/getUserByPseudo.test.js +++ b/test/unit/application/usecase/user/getUserByPseudo.test.js @@ -16,6 +16,7 @@ const mockUser = { type:'user', } const expectedUser = { + id_utilisateur:1, pseudo:'pseudo', alias:'alias', email:'testemail@gmail.com', From 283563c574c75e3d20e59dd2583b42bde92b7647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:04:53 +0100 Subject: [PATCH 37/67] swagger correction (#38) Co-authored-by: mael --- lib/domain/entity/ReviewEntity.js | 2 +- lib/domain/entity/SpotifyEntity.js | 2 +- lib/domain/entity/UserEntity.js | 2 +- .../security/JwtAccessTokenManager.js | 2 +- lib/infrastructure/webserver/server.js | 30 +- package-lock.json | 3194 +++++------------ package.json | 12 +- .../usecase/spotify/getAlbum.test.js | 2 +- 8 files changed, 866 insertions(+), 2380 deletions(-) diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index b2f3008..cc3e3e4 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -1,4 +1,4 @@ -const Joi = require('@hapi/joi') +const Joi = require('joi') const putReview = Joi.object().keys({ idOeuvre: Joi .string() diff --git a/lib/domain/entity/SpotifyEntity.js b/lib/domain/entity/SpotifyEntity.js index b3d932c..071f957 100644 --- a/lib/domain/entity/SpotifyEntity.js +++ b/lib/domain/entity/SpotifyEntity.js @@ -1,5 +1,5 @@ // Verifie le bon format de la query / payload -const Joi = require('@hapi/joi') +const Joi = require('joi') const search = Joi.object().keys({ query: Joi .string() diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index ee9151f..b549ee8 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -1,4 +1,4 @@ -const Joi = require('@hapi/joi') +const Joi = require('joi') const validationErrror = require("./utils/validationError") const userSignIn = Joi.object().keys({ email: Joi.string().max(40).required(), diff --git a/lib/infrastructure/security/JwtAccessTokenManager.js b/lib/infrastructure/security/JwtAccessTokenManager.js index 96b4fac..27b20ce 100644 --- a/lib/infrastructure/security/JwtAccessTokenManager.js +++ b/lib/infrastructure/security/JwtAccessTokenManager.js @@ -13,7 +13,7 @@ module.exports = class extends AccessTokenManager { value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user aud: 'urn:audience:test', // needs to match definition above iss: 'urn:issuer:test', // needs to match definition above, - expiresIn: '365d' + // expiresIn: '365d' }, JWT_SECRET_KEY); } diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 500a854..0ff1696 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -5,8 +5,6 @@ const Inert = require('@hapi/inert'); const Vision = require('@hapi/vision'); const Blipp = require('blipp'); const HapiSwagger = require('hapi-swagger'); -const Package = require('../../../package'); -const routes = require('../../interfaces/routes'); const serviceLocator = require('../../infrastructure/config/service-locator') const Jwt = require('@hapi/jwt'); const strategy = require("../config/strategy") @@ -24,7 +22,8 @@ const createServer = async () => { } } }); - + await server.register(Inert); + await server.register(Vision); // Register vendors plugins await server.register([ Blipp, @@ -34,32 +33,11 @@ const createServer = async () => { plugin: HapiSwagger, options: { info: { - title: 'Test API Documentation', - version: Package.version, + title: 'Solimbo', + version: '0.1' }, } }, - // { - // plugin: Good, - // options: { - // ops: { - // interval: 1000 * 60 - // }, - // reporters: { - // myConsoleReporter: [ - // { - // module: '@hapi/good-squeeze', - // name: 'Squeeze', - // args: [{ ops: '*', log: '*', error: '*', response: '*' }] - // }, - // { - // module: '@hapi/good-console' - // }, - // 'stdout' - // ] - // } - // }, - // }, { plugin: Jwt }, diff --git a/package-lock.json b/package-lock.json index 991fc5f..fc743dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,18 @@ "name": "nodejs-clean-architecture-app", "version": "1.0.0", "dependencies": { - "@hapi/boom": "^9.1.0", - "@hapi/good": "^9.0.1", - "@hapi/good-console": "^9.0.1", - "@hapi/good-squeeze": "^6.0.0", + "@hapi/boom": "^10.0.1", "@hapi/hapi": "^21.3.2", - "@hapi/hoek": "^11.0.2", "@hapi/inert": "^7.1.0", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", - "@hapi/shot": "^6.0.1", "@hapi/vision": "^7.0.3", "axios": "^1.5.1", "bcrypt": "^5.1.1", "blipp": "^4.0.2", "dotenv": "^8.6.0", "hapi-cors": "^1.0.3", - "hapi-swagger": "^17.2.0", + "hapi-swagger": "^17.2.1", "install": "^0.13.0", "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", @@ -32,7 +27,6 @@ "nodemailer": "^6.9.7", "npm": "^10.2.5", "request": "^2.88.2", - "require-directory": "^2.1.1", "sequelize": "^5.21.11", "underscore": "^1.13.6" }, @@ -48,28 +42,26 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.1.0.tgz", - "integrity": "sha512-g/VW9ZQEFJAOwAyUb8JFf7MLiLy2uEB4rU270rGzDwICxnxMlPy0O11KVePSgS36K1NI29gSlK84n5INGhd4Ag==", + "version": "11.5.4", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.5.4.tgz", + "integrity": "sha512-o2fsypTGU0WxRxbax8zQoHiIB4dyrkwYfcm8TxZ+bx9pCzcWZbQtiMqpgBvWA/nJ2TrGjK5adCLfTH8wUeU/Wg==", "dependencies": { "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.13", - "@types/lodash.clonedeep": "^4.5.7", - "js-yaml": "^4.1.0", - "lodash.clonedeep": "^4.5.0" + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" }, "engines": { "node": ">= 16" @@ -78,22 +70,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@apidevtools/openapi-schemas": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", @@ -134,22 +110,6 @@ "js-yaml": "^4.1.0" } }, - "node_modules/@apidevtools/swagger-parser/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/@apidevtools/swagger-parser/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -213,6 +173,27 @@ "node": ">=0.8.0" } }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", @@ -223,21 +204,21 @@ } }, "node_modules/@babel/core": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz", - "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.5", - "@babel/parser": "^7.23.5", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -258,23 +239,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -285,12 +249,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", - "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -300,14 +264,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -405,9 +369,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -465,14 +429,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", - "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.5", - "@babel/types": "^7.23.5" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -542,10 +506,31 @@ "node": ">=0.8.0" } }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/parser": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", - "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -717,61 +702,44 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", - "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.5", - "@babel/types": "^7.23.5", - "debug": "^4.1.0", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -797,14 +765,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/accept/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/address": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", @@ -836,18 +796,13 @@ } }, "node_modules/@hapi/boom": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.0.tgz", - "integrity": "sha512-4nZmpp4tXbm162LaZT45P7F7sgiem8dwAh2vHWT6XX24dozNjGMg6BvKCRvtCUcmcXqeMIUqWN8Rc5X8yKuROQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", "dependencies": { - "@hapi/hoek": "9.x.x" + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/boom/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, "node_modules/@hapi/bounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.1.tgz", @@ -857,14 +812,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/bounce/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/bourne": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", @@ -879,14 +826,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/call/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/catbox": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz", @@ -907,14 +846,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/catbox-memory/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/catbox-object": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@hapi/catbox-object/-/catbox-object-3.0.1.tgz", @@ -924,30 +855,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/catbox-object/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/catbox/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/catbox/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/catbox/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -965,14 +872,6 @@ "@hapi/boom": "^10.0.0" } }, - "node_modules/@hapi/content/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/cryptiles": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.1.tgz", @@ -984,14 +883,6 @@ "node": ">=14.0.0" } }, - "node_modules/@hapi/cryptiles/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/file": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz", @@ -1003,55 +894,10 @@ "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", "deprecated": "Moved to 'npm install @sideway/formula'" }, - "node_modules/@hapi/good": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@hapi/good/-/good-9.0.1.tgz", - "integrity": "sha512-zHSjw+LieqDgu5pXLUHqUJTMSgBKIdKHwxmXNoh9qVz+kSPQH463ol6OEBSTVdy0yQawKVI4eAyeqown++TpbA==", - "dependencies": { - "@hapi/hoek": "9.x.x", - "@hapi/oppsy": "3.x.x", - "@hapi/validate": "1.x.x", - "pumpify": "2.x.x" - } - }, - "node_modules/@hapi/good-console": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@hapi/good-console/-/good-console-9.0.1.tgz", - "integrity": "sha512-MbkHPDJjm2SegDG1Sb4Jtvw6uPuMbvMiPFUAQwUO0a08UFwKWt4qZ8rCV/tjoXRLEVh6CHSn45MZM4SU2Pg79g==", - "dependencies": { - "@hapi/hoek": "9.x.x", - "json-stringify-safe": "5.x.x", - "moment": "2.x.x" - } - }, - "node_modules/@hapi/good-console/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/good-squeeze": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@hapi/good-squeeze/-/good-squeeze-6.0.0.tgz", - "integrity": "sha512-UgHAF9Lm8fJPzgf2HymtowOwNc1+IL+p08YTVR+XA4d8nmyE1t9x3RLA4riqldnOKHkVqGakJ1jGqUG7jk77Cg==", - "dependencies": { - "@hapi/hoek": "9.x.x", - "fast-safe-stringify": "2.x.x" - } - }, - "node_modules/@hapi/good-squeeze/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/good/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, "node_modules/@hapi/hapi": { - "version": "21.3.2", - "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.3.2.tgz", - "integrity": "sha512-tbm0zmsdUj8iw4NzFV30FST/W4qzh/Lsw6Q5o5gAhOuoirWvxm8a4G3o60bqBw8nXvRNJ8uLtE0RKLlZINxHcQ==", + "version": "21.3.3", + "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.3.3.tgz", + "integrity": "sha512-6pgwWVl/aSKSNVn86n+mWa06jRqCAKi2adZp/Hti19A0u5x3/6eiKz8UTBPMzfrdGf9WcrYbFBYzWr/qd2s28g==", "dependencies": { "@hapi/accept": "^6.0.1", "@hapi/ammo": "^6.0.1", @@ -1076,22 +922,6 @@ "node": ">=14.15.0" } }, - "node_modules/@hapi/hapi/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/hapi/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/hapi/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1111,22 +941,6 @@ "@hapi/validate": "^2.0.1" } }, - "node_modules/@hapi/heavy/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/heavy/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/heavy/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1137,9 +951,9 @@ } }, "node_modules/@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.4.tgz", + "integrity": "sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ==" }, "node_modules/@hapi/inert": { "version": "7.1.0", @@ -1154,37 +968,13 @@ "lru-cache": "^7.14.1" } }, - "node_modules/@hapi/inert/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "node_modules/@hapi/inert/node_modules/@hapi/validate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", + "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/inert/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/inert/node_modules/@hapi/validate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", - "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==", - "dependencies": { - "@hapi/hoek": "^11.0.2", - "@hapi/topo": "^6.0.1" - } - }, - "node_modules/@hapi/inert/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" + "@hapi/hoek": "^11.0.2", + "@hapi/topo": "^6.0.1" } }, "node_modules/@hapi/iron": { @@ -1199,14 +989,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/iron/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/joi": { "version": "17.1.1", "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", @@ -1225,6 +1007,14 @@ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" }, + "node_modules/@hapi/joi/node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@hapi/jwt": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@hapi/jwt/-/jwt-3.2.0.tgz", @@ -1242,19 +1032,6 @@ "joi": "^17.2.1" } }, - "node_modules/@hapi/jwt/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/jwt/node_modules/@hapi/boom/node_modules/@hapi/hoek": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.2.tgz", - "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" - }, "node_modules/@hapi/jwt/node_modules/@hapi/hoek": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-10.0.1.tgz", @@ -1281,19 +1058,6 @@ "node": ">=14.0.0" } }, - "node_modules/@hapi/oppsy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@hapi/oppsy/-/oppsy-3.0.0.tgz", - "integrity": "sha512-0kfUEAqIi21GzFVK2snMO07znMEBiXb+/pOx1dmgOO9TuvFstcfmHU5i56aDfiFP2DM5WzQCU2UWc2gK1lMDhQ==", - "dependencies": { - "@hapi/hoek": "9.x.x" - } - }, - "node_modules/@hapi/oppsy/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, "node_modules/@hapi/pez": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.0.tgz", @@ -1306,14 +1070,6 @@ "@hapi/nigel": "^5.0.1" } }, - "node_modules/@hapi/pez/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/pinpoint": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", @@ -1329,14 +1085,6 @@ "@hapi/validate": "^2.0.1" } }, - "node_modules/@hapi/podium/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/podium/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1355,14 +1103,6 @@ "@hapi/validate": "^2.0.1" } }, - "node_modules/@hapi/shot/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/shot/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1395,22 +1135,6 @@ "@hapi/validate": "^2.0.1" } }, - "node_modules/@hapi/statehood/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/statehood/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/statehood/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1434,14 +1158,6 @@ "@hapi/wreck": "^18.0.1" } }, - "node_modules/@hapi/subtext/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/teamwork": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.0.tgz", @@ -1451,32 +1167,13 @@ } }, "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@hapi/topo/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/validate": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-1.1.3.tgz", - "integrity": "sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0" + "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/validate/node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, "node_modules/@hapi/vise": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz", @@ -1496,22 +1193,6 @@ "@hapi/validate": "^2.0.1" } }, - "node_modules/@hapi/vision/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, - "node_modules/@hapi/vision/node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@hapi/vision/node_modules/@hapi/validate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz", @@ -1531,14 +1212,6 @@ "@hapi/hoek": "^11.0.2" } }, - "node_modules/@hapi/wreck/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1555,6 +1228,28 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1629,21 +1324,6 @@ } } }, - "node_modules/@jest/core/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", @@ -1841,19 +1521,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/transform/node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@jest/types": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", @@ -1872,32 +1539,32 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1910,9 +1577,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1943,49 +1610,10 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -2011,15 +1639,6 @@ "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@sinonjs/commons": { "version": "1.8.6", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", @@ -2038,18 +1657,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2064,9 +1671,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -2083,9 +1690,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.4", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", - "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -2129,24 +1736,14 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, - "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" - }, - "node_modules/@types/lodash.clonedeep": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", - "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "node_modules/@types/node": { + "version": "20.11.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", + "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", "dependencies": { - "@types/lodash": "*" + "undici-types": "~5.26.4" } }, - "node_modules/@types/node": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.6.tgz", - "integrity": "sha512-FbNmu4F67d3oZMWBV6Y4MaPER+0EpE9eIYf2yaHhCWovc1dlXCZkqGX4NLHfVVr6umt20TNBdRzrNJIzIKfdbw==" - }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -2190,81 +1787,19 @@ "node": ">= 6.0.0" } }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dependencies": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" - } - }, - "node_modules/ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", - "dev": true, - "dependencies": { - "string-width": "^3.0.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-escapes": { @@ -2282,18 +1817,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2319,7 +1842,7 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", @@ -2352,18 +1875,14 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dependencies": { "safer-buffer": "~2.1.0" } @@ -2371,7 +1890,7 @@ "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "engines": { "node": ">=0.8" } @@ -2379,44 +1898,31 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", @@ -2509,9 +2015,9 @@ } }, "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bcrypt": { "version": "5.1.1", @@ -2529,15 +2035,15 @@ "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dependencies": { "tweetnacl": "^0.14.3" } }, "node_modules/binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "engines": { "node": ">=8" @@ -2564,62 +2070,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2642,9 +2092,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -2661,8 +2111,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -2693,45 +2143,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -2756,9 +2167,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001566", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", - "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", + "version": "1.0.30001596", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz", + "integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==", "dev": true, "funding": [ { @@ -2778,7 +2189,7 @@ "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "node_modules/chalk": { "version": "4.1.2", @@ -2795,25 +2206,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -2824,24 +2216,27 @@ } }, "node_modules/chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { - "fsevents": "~2.1.2" + "fsevents": "~2.3.2" } }, "node_modules/chownr": { @@ -2853,10 +2248,19 @@ } }, "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, "node_modules/cjs-module-lexer": { "version": "1.2.3", @@ -2864,15 +2268,6 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, - "node_modules/cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2896,19 +2291,10 @@ "node": ">=0.8" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/cls-bluebird": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "integrity": "sha512-XVb0RPmHQyy35Tz9z34gvtUcBKUK8A/1xkGCyeFc9B0C7Zr5SysgFaswRVdwI5NEMcO+3JKlIDGIOgERSn9NdA==", "dependencies": { "is-bluebird": "^1.0.2", "shimmer": "^1.1.0" @@ -2977,24 +2363,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -3010,7 +2379,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -3026,19 +2395,10 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dependencies": { "assert-plus": "^1.0.0" }, @@ -3047,25 +2407,19 @@ } }, "node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "mimic-response": "^1.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">=4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/dedent": { @@ -3074,15 +2428,6 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3104,16 +2449,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { "node": ">=0.4.0" } @@ -3123,6 +2462,14 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-libc": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", @@ -3149,18 +2496,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -3170,26 +2505,9 @@ } }, "node_modules/dottie": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", - "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==" }, "node_modules/easy-table": { "version": "1.2.0", @@ -3205,7 +2523,7 @@ "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -3220,9 +2538,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.601", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.601.tgz", - "integrity": "sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==", + "version": "1.4.695", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.695.tgz", + "integrity": "sha512-eMijZmeqPtm774pCZIOrfUHMs/7ls++W1sLhxwqgu8KQ8E2WmMtzwyqOMt0XXUJ3HTIPfuwlfwF+I5cwnfItBA==", "dev": true }, "node_modules/emittery": { @@ -3242,14 +2560,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3260,23 +2570,14 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -3322,22 +2623,10 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { "node": ">= 0.8.0" @@ -3367,25 +2656,20 @@ "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "engines": [ "node >=0.6.0" ] }, "node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fb-watchman": { "version": "2.0.2", @@ -3422,9 +2706,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -3443,22 +2727,22 @@ "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "engines": { "node": "*" } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fs-minipass": { @@ -3486,13 +2770,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "optional": true, @@ -3567,45 +2850,48 @@ } }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dependencies": { "assert-plus": "^1.0.0" } }, "node_modules/glob": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", - "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" @@ -3614,18 +2900,6 @@ "node": ">= 6" } }, - "node_modules/global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "dev": true, - "dependencies": { - "ini": "^1.3.5" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3635,28 +2909,6 @@ "node": ">=4" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3708,9 +2960,9 @@ } }, "node_modules/hapi-swagger": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-17.2.0.tgz", - "integrity": "sha512-vcLz3OK7WLFsuY7cLgPJAulnuvkGSIE3XVbeD1XzoPXtb2jmuDUTg2yvrXx32EwlhSsyT/RP1MIVzHuc8KxvQw==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-17.2.1.tgz", + "integrity": "sha512-IaF3OHfYjzDuyi5EQgS0j0xB7sbAAD4DaTwexdhPYqEBI/J7GWMXFbftGObCIOeMVDufjoSBZWeaarEkNn6/ww==", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "@hapi/boom": "^10.0.1", @@ -3728,29 +2980,21 @@ "joi": "17.x" } }, - "node_modules/hapi-swagger/node_modules/@hapi/boom": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", - "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "engines": { "node": ">=4" } }, "node_modules/har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "deprecated": "this library is no longer supported", "dependencies": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" }, "engines": { @@ -3758,12 +3002,11 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-unicode": { @@ -3771,19 +3014,10 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -3807,16 +3041,10 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -3828,9 +3056,9 @@ } }, "node_modules/http-status": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.7.3.tgz", - "integrity": "sha512-GS8tL1qHT2nBCMJDYMHGkkkKQLNkIAHz37vgO68XKvzv+XyqB4oh/DfmMHdtRzfqSJPj1xKG2TaELZtlCz6BEQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/http-status/-/http-status-1.7.4.tgz", + "integrity": "sha512-c2qSwNtTlHVYAhMj9JpGdyo0No/+DiKXCJ9pHtZ2Yf3QmPnBIytKSRT7BuyIiQ7icXLynavGmxUqkOjSrAuMuA==", "engines": { "node": ">= 0.4.0" } @@ -3847,22 +3075,6 @@ "node": ">= 6" } }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3887,21 +3099,23 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -3924,7 +3138,7 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { "node": ">=0.8.19" @@ -3933,7 +3147,7 @@ "node_modules/inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", + "integrity": "sha512-lRy4DxuIFWXlJU7ed8UiTJOSTqStqYdEb4CEbtXfNbkdj3nH1L+reUWiE10VWcJS2yR7tge8Z74pJjtBjNwj0w==", "engines": [ "node >= 0.4.0" ] @@ -3941,7 +3155,7 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3952,16 +3166,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/install": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", @@ -3991,23 +3195,11 @@ "node_modules/is-bluebird": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=", + "integrity": "sha512-PDRu1vVip5dGQg5tfn2qVCCyxbBYu5MhYUJwSfL/RoGBI97n1fxvilVazxzptZW0gcmsMH17H4EVZZI5E/RSeA==", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -4023,19 +3215,18 @@ "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-generator-fn": { @@ -4048,9 +3239,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" @@ -4059,31 +3250,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4093,24 +3259,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -4131,13 +3279,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/isemail": { "version": "2.2.1", @@ -4156,7 +3298,7 @@ "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4206,27 +3348,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4242,33 +3363,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -4283,27 +3377,10 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4461,33 +3538,6 @@ } } }, - "node_modules/jest-config/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-diff": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", @@ -4582,20 +3632,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/jest-leak-detector": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", @@ -4815,33 +3851,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", @@ -4859,21 +3868,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, "node_modules/jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", @@ -4936,15 +3930,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4961,13 +3946,13 @@ } }, "node_modules/joi": { - "version": "17.10.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.10.1.tgz", - "integrity": "sha512-vIiDxQKmRidUVp8KngT8MZSOcmRVm2zV7jbMjNYWuHcJWI0bUck3nRTGQjhpPlQenIQIBC5Vp9AhcnHbWQqafw==", + "version": "17.12.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", + "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } @@ -4977,6 +3962,14 @@ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" }, + "node_modules/joi/node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4984,13 +3977,11 @@ "dev": true }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -4999,7 +3990,7 @@ "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, "node_modules/jsesc": { "version": "2.5.2", @@ -5013,12 +4004,6 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -5026,9 +4011,9 @@ "dev": true }, "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -5038,7 +4023,7 @@ "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/json5": { "version": "2.2.3", @@ -5073,18 +4058,26 @@ "npm": ">=1.4.28" } }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "engines": [ - "node >=0.6.0" - ], + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, "node_modules/jwa": { @@ -5106,15 +4099,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -5124,18 +4108,6 @@ "node": ">=6" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5164,14 +4136,9 @@ } }, "node_modules/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.get": { "version": "4.4.2", @@ -5223,21 +4190,12 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "engines": { - "node": ">=16.14" + "node": ">=12" } }, "node_modules/make-dir": { @@ -5255,9 +4213,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -5299,24 +4257,16 @@ } }, "node_modules/mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.40.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/mime-types/node_modules/mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5326,19 +4276,10 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5347,9 +4288,12 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "5.0.0", @@ -5394,19 +4338,19 @@ } }, "node_modules/moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } }, "node_modules/moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "version": "0.5.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", "dependencies": { - "moment": ">= 2.9.0" + "moment": "^2.29.4" }, "engines": { "node": "*" @@ -5418,9 +4362,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", - "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.2.tgz", + "integrity": "sha512-3Cwg/UuRkAv/wm6RhtPE5L7JlPB877vwSF6gfLAS68H+zhH+u5oa3AieqEd0D0/kC3W7qIhYbH419f7O9i/5nw==", "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", @@ -5435,23 +4379,12 @@ "node": ">= 8.0" } }, - "node_modules/mysql2/node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, + "node_modules/mysql2/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", "engines": { - "node": ">=0.10.0" + "node": ">=16.14" } }, "node_modules/named-placeholders": { @@ -5465,14 +4398,6 @@ "node": ">=12.0.0" } }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5508,25 +4433,6 @@ } } }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5540,30 +4446,29 @@ "dev": true }, "node_modules/nodemailer": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", - "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.11.tgz", + "integrity": "sha512-UiAkgiERuG94kl/3bKfE8o10epvDnl0vokNEtZDPTq9BWzIl6EFT9336SbIT4oaTBD8NmmUTLsQyXHV82eXSWg==", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz", - "integrity": "sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, - "hasInstallScript": true, "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", + "chokidar": "^3.5.2", + "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^4.0.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -5576,11 +4481,49 @@ "url": "https://opencollective.com/nodemon" } }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dependencies": { "abbrev": "1" }, @@ -5588,7 +4531,7 @@ "nopt": "bin/nopt.js" }, "engines": { - "node": "*" + "node": ">=6" } }, "node_modules/normalize-path": { @@ -5600,19 +4543,10 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/npm": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.2.5.tgz", - "integrity": "sha512-lXdZ7titEN8CH5YJk9C/aYRU9JeDxQ4d8rwIIDsvH3SMjLjHTukB2CFstMiB30zXs4vCrPN2WH6cDq1yHBeJAw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.5.0.tgz", + "integrity": "sha512-Ejxwvfh9YnWVU2yA5FzoYLTW52vxHCz+MHrOFg9Cc8IFgF/6f5AGPAvb5WTay5DIUP1NIfN3VBZ0cLlGO0Ys+A==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -5676,7 +4610,6 @@ "semver", "spdx-expression-parse", "ssri", - "strip-ansi", "supports-color", "tar", "text-table", @@ -5693,12 +4626,12 @@ "@npmcli/fs": "^3.1.0", "@npmcli/map-workspaces": "^3.0.4", "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.2", - "@sigstore/tuf": "^2.2.0", + "@npmcli/promise-spawn": "^7.0.1", + "@npmcli/run-script": "^7.0.4", + "@sigstore/tuf": "^2.3.1", "abbrev": "^2.0.0", "archy": "~1.0.0", - "cacache": "^18.0.1", + "cacache": "^18.0.2", "chalk": "^5.3.0", "ci-info": "^4.0.0", "cli-columns": "^4.0.0", @@ -5741,15 +4674,14 @@ "npm-user-validate": "^2.0.0", "npmlog": "^7.0.1", "p-map": "^4.0.0", - "pacote": "^17.0.5", + "pacote": "^17.0.6", "parse-conflict-json": "^3.0.1", "proc-log": "^3.0.0", "qrcode-terminal": "^0.12.0", "read": "^2.1.0", - "semver": "^7.5.4", + "semver": "^7.6.0", "spdx-expression-parse": "^3.0.1", "ssri": "^10.0.5", - "strip-ansi": "^7.1.0", "supports-color": "^9.4.0", "tar": "^6.2.0", "text-table": "~0.2.0", @@ -5804,6 +4736,17 @@ "node": ">=12" } }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "inBundle": true, @@ -5825,13 +4768,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.0", + "version": "2.2.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -5846,7 +4803,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.2.2", + "version": "7.4.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -5858,7 +4815,7 @@ "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.1", + "@npmcli/query": "^3.1.0", "@npmcli/run-script": "^7.0.2", "bin-links": "^4.0.1", "cacache": "^18.0.0", @@ -5877,7 +4834,7 @@ "parse-conflict-json": "^3.0.0", "proc-log": "^3.0.0", "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.2", + "promise-call-limit": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "ssri": "^10.0.5", @@ -5892,7 +4849,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.0.3", + "version": "8.2.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -5946,7 +4903,7 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.3", + "version": "5.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -6040,7 +4997,7 @@ } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.0", + "version": "7.0.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -6051,7 +5008,7 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.1", + "version": "3.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -6062,14 +5019,14 @@ } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "7.0.2", + "version": "7.0.4", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.0", "node-gyp": "^10.0.0", - "read-package-json-fast": "^3.0.0", "which": "^4.0.0" }, "engines": { @@ -6086,18 +5043,26 @@ } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.0", + "version": "2.2.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/protobuf-specs": "^0.3.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "1.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", + "version": "0.3.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -6105,12 +5070,13 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.2.0", + "version": "2.2.3", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", "make-fetch-happen": "^13.0.0" }, "engines": { @@ -6118,12 +5084,25 @@ } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.2.0", + "version": "2.3.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "1.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", - "tuf-js": "^2.1.0" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -6157,17 +5136,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/abort-controller": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/npm/node_modules/agent-base": { "version": "7.1.0", "inBundle": true, @@ -6192,14 +5160,11 @@ } }, "node_modules/npm/node_modules/ansi-regex": { - "version": "6.0.1", + "version": "5.0.1", "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/npm/node_modules/ansi-styles": { @@ -6224,13 +5189,9 @@ "license": "MIT" }, "node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.1", + "version": "4.0.2", "inBundle": true, "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^4.1.0" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -6240,25 +5201,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/base64-js": { - "version": "1.5.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/bin-links": { "version": "4.0.3", "inBundle": true, @@ -6289,29 +5231,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/npm/node_modules/buffer": { - "version": "6.0.3", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/npm/node_modules/builtins": { "version": "5.0.1", "inBundle": true, @@ -6321,7 +5240,7 @@ } }, "node_modules/npm/node_modules/cacache": { - "version": "18.0.1", + "version": "18.0.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -6406,25 +5325,6 @@ "node": ">= 10" } }, - "node_modules/npm/node_modules/cli-columns/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cli-columns/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/cli-table3": { "version": "0.6.3", "inBundle": true, @@ -6491,25 +5391,6 @@ "node": ">=8.0.0" } }, - "node_modules/npm/node_modules/columnify/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/columnify/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", "inBundle": true, @@ -6590,13 +5471,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/delegates": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "5.2.0", "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -6635,22 +5511,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/event-target-shim": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/events": { - "version": "3.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.1", "inBundle": true, @@ -6716,25 +5576,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/gauge/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/gauge/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/glob": { "version": "10.3.10", "inBundle": true, @@ -6767,7 +5608,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "MIT", "dependencies": { @@ -6794,7 +5635,7 @@ "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.0", + "version": "7.0.2", "inBundle": true, "license": "MIT", "dependencies": { @@ -6806,7 +5647,7 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.2", + "version": "7.0.4", "inBundle": true, "license": "MIT", "dependencies": { @@ -6829,25 +5670,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm/node_modules/ieee754": { - "version": "1.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ignore-walk": { "version": "6.0.4", "inBundle": true, @@ -6900,10 +5722,22 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", @@ -6973,6 +5807,11 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "3.0.1", "inBundle": true, @@ -7020,7 +5859,7 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.4", + "version": "6.0.7", "inBundle": true, "license": "ISC", "dependencies": { @@ -7039,7 +5878,7 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.5", + "version": "7.0.8", "inBundle": true, "license": "ISC", "dependencies": { @@ -7060,7 +5899,7 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.2", + "version": "5.0.5", "inBundle": true, "license": "ISC", "dependencies": { @@ -7095,7 +5934,7 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.4", + "version": "6.0.7", "inBundle": true, "license": "ISC", "dependencies": { @@ -7109,7 +5948,7 @@ } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.3", + "version": "9.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -7119,7 +5958,7 @@ "npm-registry-fetch": "^16.0.0", "proc-log": "^3.0.0", "semver": "^7.3.7", - "sigstore": "^2.1.0", + "sigstore": "^2.2.0", "ssri": "^10.0.5" }, "engines": { @@ -7165,7 +6004,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.1.0", + "version": "10.2.0", "inBundle": true, "license": "ISC", "engines": { @@ -7487,7 +6326,7 @@ } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "8.0.1", + "version": "8.0.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -7577,7 +6416,7 @@ } }, "node_modules/npm/node_modules/pacote": { - "version": "17.0.5", + "version": "17.0.6", "inBundle": true, "license": "ISC", "dependencies": { @@ -7596,7 +6435,7 @@ "promise-retry": "^2.0.1", "read-package-json": "^7.0.0", "read-package-json-fast": "^3.0.0", - "sigstore": "^2.0.0", + "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, @@ -7644,7 +6483,7 @@ } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.13", + "version": "6.0.15", "inBundle": true, "license": "MIT", "dependencies": { @@ -7663,14 +6502,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/process": { - "version": "0.11.10", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", "inBundle": true, @@ -7680,7 +6511,7 @@ } }, "node_modules/npm/node_modules/promise-call-limit": { - "version": "1.0.2", + "version": "3.0.1", "inBundle": true, "license": "ISC", "funding": { @@ -7767,21 +6598,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/readable-stream": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", "inBundle": true, @@ -7790,25 +6606,6 @@ "node": ">= 4" } }, - "node_modules/npm/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "inBundle": true, @@ -7816,7 +6613,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -7876,14 +6673,16 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.1.0", + "version": "2.2.2", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.1.0", - "@sigstore/tuf": "^2.1.0" + "@sigstore/bundle": "^2.2.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.0", + "@sigstore/sign": "^2.2.3", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.1.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -7899,15 +6698,15 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.0", "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 16.0.0", "npm": ">= 3.0.0" } }, @@ -7934,7 +6733,7 @@ } }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "inBundle": true, "license": "CC-BY-3.0" }, @@ -7948,7 +6747,7 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.16", + "version": "3.0.17", "inBundle": true, "license": "CC0-1.0" }, @@ -7963,14 +6762,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/string_decoder": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", "inBundle": true, @@ -7998,15 +6789,19 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/npm/node_modules/string-width-cjs/node_modules/strip-ansi": { + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "inBundle": true, "license": "MIT", @@ -8017,61 +6812,8 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "7.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", "inBundle": true, "license": "MIT", "engines": { @@ -8146,7 +6888,7 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "2.1.0", + "version": "2.2.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -8281,14 +7023,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", "inBundle": true, @@ -8303,15 +7037,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { @@ -8335,6 +7069,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/npm/node_modules/write-file-atomic": { "version": "5.0.1", "inBundle": true, @@ -8382,7 +7130,7 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { "wrappy": "1" } @@ -8408,15 +7156,6 @@ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "peer": true }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8468,30 +7207,6 @@ "node": ">=6" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -8522,7 +7237,7 @@ "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "engines": { "node": ">=0.10.0" } @@ -8545,7 +7260,7 @@ "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "node_modules/picocolors": { "version": "1.0.0", @@ -8586,15 +7301,6 @@ "node": ">=8" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -8641,9 +7347,9 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/pstree.remy": { "version": "1.1.8", @@ -8651,74 +7357,22 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -8739,9 +7393,9 @@ } }, "node_modules/readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { "picomatch": "^2.2.1" @@ -8750,30 +7404,6 @@ "node": ">=8.10.0" } }, - "node_modules/registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -8805,22 +7435,24 @@ "node": ">= 6" } }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=0.8" + "node": ">= 0.12" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -8872,15 +7504,6 @@ "node": ">=10" } }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", @@ -8904,9 +7527,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -8914,32 +7551,28 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "semver": "^6.3.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/seq-queue": { @@ -8948,9 +7581,9 @@ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, "node_modules/sequelize": { - "version": "5.21.11", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.11.tgz", - "integrity": "sha512-ZJw3Hp+NS7iHcTz4fHlKvIBm4I7xYibYRCP4HhSyMB26xgqFYFOXTaeWbHD2UUwAFaksTLw5ntzfpA9kE33SVA==", + "version": "5.22.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.22.5.tgz", + "integrity": "sha512-ySIHof18sJbeVG4zjEvsDL490cd9S14/IhkCrZR/g0C/FPlZq1AzEJVeSAo++9/sgJH2eERltAIGqYQNgVqX/A==", "deprecated": "Please update to v6 or higher! A migration guide can be found here: https://sequelize.org/v6/manual/upgrade-to-v6.html", "dependencies": { "bluebird": "^3.5.0", @@ -8965,8 +7598,8 @@ "semver": "^6.3.0", "sequelize-pool": "^2.3.0", "toposort-class": "^1.0.1", - "uuid": "^3.3.3", - "validator": "^10.11.0", + "uuid": "^8.3.2", + "validator": "^13.7.0", "wkx": "^0.4.8" }, "engines": { @@ -8992,27 +7625,26 @@ "node": ">= 6.0.0" } }, - "node_modules/sequelize/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/sequelize/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, + "node_modules/sequelize/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -9045,6 +7677,27 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -9093,9 +7746,9 @@ } }, "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -9128,17 +7781,12 @@ "node": ">=10" } }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "node_modules/string-length": { @@ -9167,14 +7815,6 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -9205,24 +7845,26 @@ } }, "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-hyperlinks": { @@ -9238,27 +7880,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -9283,9 +7904,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.10.3", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.10.3.tgz", - "integrity": "sha512-fu3aozjxFWsmcO1vyt1q1Ji2kN7KlTd1vHy27E9WgPyXo9nrEzhQPqgxaAjbMsOmb8XFKNGo4Sa3Q+84Fh+pFw==" + "version": "5.11.10", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.10.tgz", + "integrity": "sha512-wAHf32iFqJCBkdQRBYB1pR8kJuliJbgCXcdgkU7GkDvrOfD2gVmyEwdTi9rERCur/OrufifnH5UecOzlQ07CYg==" }, "node_modules/tar": { "version": "6.2.0", @@ -9303,18 +7924,6 @@ "node": ">=10" } }, - "node_modules/term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -9360,15 +7969,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9405,7 +8005,7 @@ "node_modules/toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==" }, "node_modules/touch": { "version": "3.1.0", @@ -9419,10 +8019,42 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/touch/node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -9433,7 +8065,7 @@ "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, "node_modules/type-detect": { "version": "4.0.8", @@ -9445,21 +8077,15 @@ } }, "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/uglify-js": { @@ -9475,27 +8101,9 @@ } }, "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "dependencies": { - "debug": "^2.2.0" - } - }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/undefsafe/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, "node_modules/underscore": { @@ -9503,17 +8111,10 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/update-browserslist-db": { "version": "1.0.13", @@ -9545,96 +8146,23 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", - "dev": true, - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "bin": { "uuid": "bin/uuid" @@ -9661,9 +8189,9 @@ "dev": true }, "node_modules/validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", "engines": { "node": ">= 0.10" } @@ -9671,7 +8199,7 @@ "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "engines": [ "node >=0.6.0" ], @@ -9699,6 +8227,20 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9722,18 +8264,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wkx": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", @@ -9767,27 +8297,19 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "dependencies": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, + "signal-exit": "^3.0.7" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/y18n": { @@ -9861,14 +8383,6 @@ "optionalDependencies": { "commander": "^9.4.1" } - }, - "node_modules/z-schema/node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", - "engines": { - "node": ">= 0.10" - } } } } diff --git a/package.json b/package.json index 2ccdb3f..5ab0ccf 100644 --- a/package.json +++ b/package.json @@ -12,31 +12,25 @@ "husky": "node ./node_modules/husky/lib/bin.js" }, "dependencies": { - "@hapi/boom": "^9.1.0", - "@hapi/good": "^9.0.1", - "@hapi/good-console": "^9.0.1", - "@hapi/good-squeeze": "^6.0.0", + "@hapi/boom": "^10.0.1", "@hapi/hapi": "^21.3.2", - "@hapi/hoek": "^11.0.2", "@hapi/inert": "^7.1.0", "@hapi/joi": "^17.1.1", "@hapi/jwt": "^3.2.0", - "@hapi/shot": "^6.0.1", "@hapi/vision": "^7.0.3", "axios": "^1.5.1", "bcrypt": "^5.1.1", "blipp": "^4.0.2", "dotenv": "^8.6.0", "hapi-cors": "^1.0.3", - "hapi-swagger": "^17.2.0", + "mysql2": "^3.6.0", + "hapi-swagger": "^17.2.1", "install": "^0.13.0", "joi": "^17.10.0", "jsonwebtoken": "^8.5.1", - "mysql2": "^3.6.0", "nodemailer": "^6.9.7", "npm": "^10.2.5", "request": "^2.88.2", - "require-directory": "^2.1.1", "sequelize": "^5.21.11", "underscore": "^1.13.6" }, diff --git a/test/unit/application/usecase/spotify/getAlbum.test.js b/test/unit/application/usecase/spotify/getAlbum.test.js index b93a8d2..0516400 100644 --- a/test/unit/application/usecase/spotify/getAlbum.test.js +++ b/test/unit/application/usecase/spotify/getAlbum.test.js @@ -1,4 +1,4 @@ -const getAlbum = require("../../../../../lib/application/use_cases/spotify/getalbum") +const getAlbum = require("../../../../../lib/application/use_cases/spotify/getAlbum") const { albumRawOneArtist, From 24e264b13064bfeafb3024d6bfc634fe718b536a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:50:47 +0100 Subject: [PATCH 38/67] added like oeuvre endpoint (#39) --- .../use_cases/oeuvre/likeOeuvre.js | 13 ++ lib/domain/entity/OeuvreEntity.js | 16 +++ lib/infrastructure/config/service-locator.js | 7 +- .../repositories/LikeOeuvreRepository.js | 44 ++++++ .../LikeOeuvreRepositoryAbstract.js | 19 +++ lib/infrastructure/webserver/server.js | 1 + .../controllers/OeuvreController.js | 23 ++++ lib/interfaces/routes/oeuvre.js | 39 ++++++ test/integration/oeuvre.test.js | 129 ++++++++++++++++++ .../usecase/oeuvre/likeOeuvre.test.js | 91 ++++++++++++ 10 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 lib/application/use_cases/oeuvre/likeOeuvre.js create mode 100644 lib/domain/entity/OeuvreEntity.js create mode 100644 lib/infrastructure/repositories/LikeOeuvreRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/LikeOeuvreRepositoryAbstract.js create mode 100644 lib/interfaces/controllers/OeuvreController.js create mode 100644 lib/interfaces/routes/oeuvre.js create mode 100644 test/integration/oeuvre.test.js create mode 100644 test/unit/application/usecase/oeuvre/likeOeuvre.test.js diff --git a/lib/application/use_cases/oeuvre/likeOeuvre.js b/lib/application/use_cases/oeuvre/likeOeuvre.js new file mode 100644 index 0000000..2e12fed --- /dev/null +++ b/lib/application/use_cases/oeuvre/likeOeuvre.js @@ -0,0 +1,13 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (userToken, artistId,type, {userRepository,likeOeuvreRepository,accessTokenManager,spotifyRepository}) =>{ + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + const oeuvre = spotifyRepository.getOeuvre(artistId,type) + if(oeuvre.error) throwStatusCode(oeuvre.error.status,oeuvre.error.message) + if(! await likeOeuvreRepository.doesUserLikes(id_utilisateur,artistId)) { + await likeOeuvreRepository.like(id_utilisateur,artistId) + return true + } + await likeOeuvreRepository.unlike(id_utilisateur,artistId) + return false +} \ No newline at end of file diff --git a/lib/domain/entity/OeuvreEntity.js b/lib/domain/entity/OeuvreEntity.js new file mode 100644 index 0000000..1e4fb1f --- /dev/null +++ b/lib/domain/entity/OeuvreEntity.js @@ -0,0 +1,16 @@ +const Joi = require('joi') + +const likeOeuvre = Joi.object().keys({ + type: Joi + .string() + .required() + .custom((value, helpers) => { + const allowedValues = ["track", "album", "artist","single","compilation"] + return allowedValues.includes(value) ? value : helpers.error('any.invalid') + }), + id: Joi.string().max(50).required() +}) + +module.exports = { + likeOeuvre +} \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index d6f76dc..718b23e 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -1,7 +1,6 @@ 'use strict'; -const constants = require('./constants'); -const environment = require('./environment'); + const UserRepository= require('../repositories/UserRepository'); const spotifyRepository= require('../repositories/SpotifyRepository'); const JwtAccessTokenManager = require('../security/JwtAccessTokenManager'); @@ -10,6 +9,7 @@ const NodemailerRepository= require('../repositories/NodemailerRepository'); const FollowRepository= require('../repositories/FollowRepository'); const FriendRepository= require('../repositories/FriendRepository'); const ReviewRepository= require('../repositories/ReviewRepository'); +const LikeOeuvreRepository= require('../repositories/LikeOeuvreRepository'); function buildBeans() { return { @@ -20,7 +20,8 @@ function buildBeans() { mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), followRepository: new FollowRepository(), friendRepository: new FriendRepository(), - reviewRepository: new ReviewRepository() + reviewRepository: new ReviewRepository(), + likeOeuvreRepository: new LikeOeuvreRepository() }; } diff --git a/lib/infrastructure/repositories/LikeOeuvreRepository.js b/lib/infrastructure/repositories/LikeOeuvreRepository.js new file mode 100644 index 0000000..4e6597f --- /dev/null +++ b/lib/infrastructure/repositories/LikeOeuvreRepository.js @@ -0,0 +1,44 @@ +'use strict'; +const sequelize = require('../orm/sequelize/sequelize'); +const LikeOeuvreRepositoryAbstract = require('./interfaces/LikeOeuvreRepositoryAbstract') +module.exports = class extends LikeOeuvreRepositoryAbstract { + constructor() { + super(); + this.db = sequelize; + this.likeOeuvre = this.db.model('like_oeuvre'); + } + + like(userId, oeuvreId) { + this.likeOeuvre.create({ + id_oeuvre: oeuvreId, + id_user: userId + }) + } + + unlike(userId, oeuvreId) { + this.likeOeuvre.destroy({ + where: { + id_oeuvre: oeuvreId, + id_user: userId + } + }) + } + doesUserLikes(userId, oeuvreId) { + return this.likeOeuvre.findOne({ + where: { + id_oeuvre: oeuvreId, + id_user: userId + } + }).then(like => { + return !!like + }) + } + getLikeCount(oeuvreId) { + return this.likeOeuvre.cout({ + where: { + id_oeuvre: oeuvreId, + } + }).then(count => count) + } + +}; diff --git a/lib/infrastructure/repositories/interfaces/LikeOeuvreRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/LikeOeuvreRepositoryAbstract.js new file mode 100644 index 0000000..7629927 --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/LikeOeuvreRepositoryAbstract.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = class { + + like(userId, oeuvreId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + + unlike(userId, oeuvreId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + doesUserLikes(userId, oeuvreId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getLikeCount(oeuvreId) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + +}; diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 0ff1696..1fb03ab 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -65,6 +65,7 @@ const createServer = async () => { require('../../interfaces/routes/upload'), require('../../interfaces/routes/friends'), require('../../interfaces/routes/artist'), + require('../../interfaces/routes/oeuvre'), ]); refreshTokens(serviceLocator) removeExpiredConfirmTokens(serviceLocator) diff --git a/lib/interfaces/controllers/OeuvreController.js b/lib/interfaces/controllers/OeuvreController.js new file mode 100644 index 0000000..e7f3b7b --- /dev/null +++ b/lib/interfaces/controllers/OeuvreController.js @@ -0,0 +1,23 @@ +'use strict'; + +const likeOeuvre = require('../../application/use_cases/oeuvre/likeOeuvre'); +const handleError = require("./utils/handleError"); + +module.exports = { + + async like(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {id,type} = request.params + try{ + await likeOeuvre(token,id,type, serviceLocator) + return handler.response("").code(200) + } + catch (e){ + return handleError(e) + } + }, + + +} diff --git a/lib/interfaces/routes/oeuvre.js b/lib/interfaces/routes/oeuvre.js new file mode 100644 index 0000000..2a202a9 --- /dev/null +++ b/lib/interfaces/routes/oeuvre.js @@ -0,0 +1,39 @@ +const ArtistController = require("../controllers/OeuvreController.js"); +const { + likeOeuvre +} = require("../../domain/entity/OeuvreEntity.js"); +module.exports = { + name: 'oeuvre', + version: '1.0.0', + register: async (server) => { + server.route([ + { + method: 'POST', + path: '/oeuvre/{type}/{id}/like', + handler: ArtistController.like, + options: { + auth: 'jwt', + description: 'like/unlike an oeuvre', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: likeOeuvre + } + }, + }, + ]); + } +}; \ No newline at end of file diff --git a/test/integration/oeuvre.test.js b/test/integration/oeuvre.test.js new file mode 100644 index 0000000..bdfb448 --- /dev/null +++ b/test/integration/oeuvre.test.js @@ -0,0 +1,129 @@ +'use strict'; +const Hapi = require('@hapi/hapi'); +const strategy = require("../../lib/infrastructure/config/strategy"); +const Jwt = require("@hapi/jwt"); +const jwt = require('jsonwebtoken'); + +require('dotenv').config() +let server +const mockUserRepository = {} +const mockLikeOeuvreRepository = {} +const mockAccessTokenManager = {} +const mockSpotifyRepository = {} + +mockAccessTokenManager.generate = ((test) =>{return ''}) +const mockToken = jwt.sign({ + sub: 'my-sub', + value: 1, + aud: 'urn:audience:test', + iss: 'urn:issuer:test', + expiresIn: '365d' +}, process.env.SECRET_ENCODER) + +describe('artist route', () => { + + beforeEach(async () => { + + server = Hapi.server({ + port: process.env.PORT || 3000 + }); + server.app.serviceLocator = { + userRepository: mockUserRepository, + likeOeuvreRepository: mockLikeOeuvreRepository, + accessTokenManager: mockAccessTokenManager, + spotifyRepository: mockSpotifyRepository + } + server.register(Jwt) + server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); + await server.register([ + require('../../lib/interfaces/routes/oeuvre'), + ]); + }); + + afterEach(async () => { + jest.clearAllMocks(); + await server.stop(); + }); + describe("POST oeuvre/{type}/{id}/like route", ()=>{ + const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + } + const mockArtist = { + external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, + genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], + id: '4FpJcNgOvIpSBeJgRg3OfN', + images: [ + { + height: 640, + url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', + width: 640 + }, + ], + name: 'Orelsan', + popularity: 64, + type: 'artist', + } + const errror = { + error: { + status: 400, + message: "message", + } + } + it("should return status code 200", async () => { + + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) + mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(false) + mockLikeOeuvreRepository.like = jest.fn() + const res1 = await server.inject({ + method: 'POST', + url: `/oeuvre/artist/4FpJcNgOvIpSBeJgRg3OfN/like`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(200); + + }) + it("should return status code 401", async () => { + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(null) + const res1 = await server.inject({ + method: 'POST', + url: `/oeuvre/artist/4FpJcNgOvIpSBeJgRg3OfN/like`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(401); + + }) + it("should return status code 400", async () => { + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(errror) + const res1 = await server.inject({ + method: 'POST', + url: `/oeuvre/artist/4FpJcNgOvIpSBeJgRg3OfN/like`, + headers: { + Authorization: `Bearer ${mockToken}` + } + }); + expect(res1.statusCode).toBe(400); + + }) + + }) + + +}); diff --git a/test/unit/application/usecase/oeuvre/likeOeuvre.test.js b/test/unit/application/usecase/oeuvre/likeOeuvre.test.js new file mode 100644 index 0000000..18b8421 --- /dev/null +++ b/test/unit/application/usecase/oeuvre/likeOeuvre.test.js @@ -0,0 +1,91 @@ +const catchError = require("../utils/catchError") +const likeOeuvre = require("../../../../../lib/application/use_cases/oeuvre/likeOeuvre") +const mockUserRepository = {} +const mockLikeOeuvreRepository = {} +const mockAccessTokenManager = {} +const mockSpotifyRepository = {} + +const serviceLocator = { + userRepository: mockUserRepository, + likeOeuvreRepository: mockLikeOeuvreRepository, + accessTokenManager: mockAccessTokenManager, + spotifyRepository: mockSpotifyRepository +} + +describe("like oeuvre use case ", ()=>{ + const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false + } + const mockArtist = { + external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, + genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], + id: '4FpJcNgOvIpSBeJgRg3OfN', + images: [ + { + height: 640, + url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', + width: 640 + }, + ], + name: 'Orelsan', + popularity: 64, + type: 'artist', + } + const errror = { + error: { + status: 400, + message: "message", + } + } + describe("valid usecase", ()=>{ + + it("should return true", async ()=>{ + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) + mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(false) + mockLikeOeuvreRepository.like = jest.fn() + const result = await likeOeuvre(1,1,"artist",serviceLocator) + expect(result).toBe(true) + }) + + it("should return false", async ()=>{ + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) + mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(true) + mockLikeOeuvreRepository.unlike = jest.fn() + const result = await likeOeuvre(1,1,"artist",serviceLocator) + expect(result).toBe(false) + }) + }) + + describe("invalid usecase", ()=>{ + it("should return error 401", async ()=>{ + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(null) + const error = await catchError(async ()=>{ + await likeOeuvre(1,1,"artist", serviceLocator) + }) + expect(error.code).toBe(401) + }) + it("should return error 400", async ()=>{ + mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) + mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(errror) + const error = await catchError(async ()=>{ + await likeOeuvre(1,1,"artist", serviceLocator) + }) + expect(error.code).toBe(400) + }) + }) +}) \ No newline at end of file From 6323a6032fec9d60241c7dd46cdc7aa58c6777ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:02:42 +0100 Subject: [PATCH 39/67] correction getUserByConfirmToken (#40) --- lib/application/use_cases/user/getUserByConfirmToken.js | 2 +- test/integration/users.test.js | 4 ++-- .../application/usecase/user/getUserByConfirmToken.test.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/application/use_cases/user/getUserByConfirmToken.js b/lib/application/use_cases/user/getUserByConfirmToken.js index 7dd23b2..070e217 100644 --- a/lib/application/use_cases/user/getUserByConfirmToken.js +++ b/lib/application/use_cases/user/getUserByConfirmToken.js @@ -1,5 +1,5 @@ const UserPublic = require("../../../domain/model/UserPublic") module.exports = async (confirmToken,{userRepository}) =>{ - const user = userRepository.getUserByConfirmToken(confirmToken) + const user = await userRepository.getByConfirmToken(confirmToken) return user ? new UserPublic(user) : null } \ No newline at end of file diff --git a/test/integration/users.test.js b/test/integration/users.test.js index 5e03b05..b6d30a8 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -307,7 +307,7 @@ describe('user route', () => { }) describe("/users/getUserByConfirmToken", ()=> { it('should return valid code 200', async ()=>{ - mockUserRepository.getUserByConfirmToken = jest.fn(()=>{ + mockUserRepository.getByConfirmToken = jest.fn(()=>{ return {id_utilisateur:1} }) const res = await server.inject({ @@ -317,7 +317,7 @@ describe('user route', () => { expect(res.statusCode).toBe(200); }) it('should return invalid code 403', async ()=>{ - mockUserRepository.getUserByConfirmToken = jest.fn(()=>{ + mockUserRepository.getByConfirmToken = jest.fn(()=>{ return null }) const res = await server.inject({ diff --git a/test/unit/application/usecase/user/getUserByConfirmToken.test.js b/test/unit/application/usecase/user/getUserByConfirmToken.test.js index fbe79b2..2137b7f 100644 --- a/test/unit/application/usecase/user/getUserByConfirmToken.test.js +++ b/test/unit/application/usecase/user/getUserByConfirmToken.test.js @@ -33,12 +33,12 @@ describe('getUserByPseudo',()=>{ }); it('should return Public User',async ()=>{ console.log("test") - mockUserRepository.getUserByConfirmToken = jest.fn(()=>mockUser) + mockUserRepository.getByConfirmToken = jest.fn(()=>mockUser) const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) expect(result).toEqual(expectedUser) }) it('should return null',async ()=>{ - mockUserRepository.getUserByConfirmToken = jest.fn(()=>null) + mockUserRepository.getByConfirmToken = jest.fn(()=>null) const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) expect(result).toBeNull() }) From 9795aca3ccb6ce5a8a097e58e68b723f7e06c541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:37:08 +0100 Subject: [PATCH 40/67] removed limit (#41) --- lib/application/use_cases/artist/getArtist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/application/use_cases/artist/getArtist.js b/lib/application/use_cases/artist/getArtist.js index 47f7691..be5c69e 100644 --- a/lib/application/use_cases/artist/getArtist.js +++ b/lib/application/use_cases/artist/getArtist.js @@ -12,7 +12,7 @@ module.exports = async (artistId, userToken, {accessTokenManager,userRepository, if(artist.error) throwStatusCode(artist.error.status,artist.error.message) const doesUserFollow = await followRepository.doesFollows(id_utilisateur,artistId) - const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single',50) + const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single') const album_ids = albums.items.map((album)=>album.id) const [ From 22501cce7f69fa46cdd890919733282d9fb76a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Tue, 12 Mar 2024 18:39:46 +0100 Subject: [PATCH 41/67] isUser correction --- lib/application/use_cases/user/getUserByPseudo.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/application/use_cases/user/getUserByPseudo.js b/lib/application/use_cases/user/getUserByPseudo.js index 65bfaa5..9f34bb3 100644 --- a/lib/application/use_cases/user/getUserByPseudo.js +++ b/lib/application/use_cases/user/getUserByPseudo.js @@ -1,5 +1,7 @@ const UserPublic = require("../../../domain/model/UserPublic") module.exports = async (pseudo,{userRepository}) =>{ - const user = userRepository.getByEmailOrPseudo(pseudo,pseudo) + const user = await userRepository.getByEmailOrPseudo(pseudo,pseudo) + console.log(!!user) + console.log(user) return user ? new UserPublic(user) : null } \ No newline at end of file From 70e5f29a499882e11b7066e1275620595ba205ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:50:04 +0100 Subject: [PATCH 42/67] correction (#42) --- lib/application/use_cases/review/getReview.js | 4 ---- lib/interfaces/controllers/UsersController.js | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/application/use_cases/review/getReview.js b/lib/application/use_cases/review/getReview.js index bca002c..c926377 100644 --- a/lib/application/use_cases/review/getReview.js +++ b/lib/application/use_cases/review/getReview.js @@ -7,10 +7,6 @@ module.exports = async (idReview,userToken, {reviewRepository, spotifyRepository const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - - if(userToken) - userToken = accessTokenManager.decode(userToken)?.value - let doesUserLike = false if(userToken) { const id_utilisateur = accessTokenManager.decode(userToken)?.value diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index 2f74d46..bb86b5a 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -23,8 +23,7 @@ module.exports = { // Input let { pseudo, photo, alias, bio,confirmToken } = request.payload - const user = await CompleteAccount(pseudo, alias, bio,photo,confirmToken, serviceLocator); - return handler.response('Votre compte a bien été validé').code(200) + return handler.response(await CompleteAccount(pseudo, alias, bio,photo,confirmToken, serviceLocator)).code(200) }catch(error){ return handleError(error) } From 8e725399dd8ff603ce0ed7a85038d5e3af8f2ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:34:29 +0100 Subject: [PATCH 43/67] replaced email by user in signin (#43) --- .../use_cases/security/GetAccessToken.js | 6 +++++- lib/domain/model/UserPublic.js | 1 + lib/interfaces/controllers/UsersController.js | 8 ++------ .../usecase/user/getAccessToken.test.js | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/application/use_cases/security/GetAccessToken.js b/lib/application/use_cases/security/GetAccessToken.js index dd98e83..9a5f9f4 100644 --- a/lib/application/use_cases/security/GetAccessToken.js +++ b/lib/application/use_cases/security/GetAccessToken.js @@ -1,11 +1,15 @@ 'use strict'; const throwStatusCode = require("../utils/throwStatusCode") const bcrypt = require("bcrypt"); +const userPublic = require("../../../domain/model/UserPublic") module.exports = async (email, password, { userRepository, accessTokenManager }) => { const user = await userRepository.getByIdent(email); if (!user || !await bcrypt.compare(password,user.password)) { throwStatusCode(401,'Bad credentials') } + return { + user : new userPublic(user), + token: accessTokenManager.generate(user) + } - return accessTokenManager.generate(user); }; diff --git a/lib/domain/model/UserPublic.js b/lib/domain/model/UserPublic.js index a3c3ce9..c21c9dc 100644 --- a/lib/domain/model/UserPublic.js +++ b/lib/domain/model/UserPublic.js @@ -8,6 +8,7 @@ module.exports = class { this.email = userRaw?.email; this.alias = userRaw?.alias this.photo = userRaw?.photo + this.bio = userRaw?.bio this.photo_temporaire = userRaw?.photo_temporaire this.id_role = userRaw?.id_role; this.ban_until = userRaw?.ban_until diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index bb86b5a..31b7c6f 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -34,11 +34,7 @@ module.exports = { const serviceLocator = request.server.app.serviceLocator; const {email, password} = request.payload try{ - return handler - .response({ - email : email, - token: await GetAccessToken(email,password,serviceLocator) - }) + return handler.response(await GetAccessToken(email,password,serviceLocator)).code(200) } catch (error){ return handleError(error) @@ -52,7 +48,7 @@ module.exports = { try{ return handler.response({ path: `${request.server.info.uri}/${await uploadPreview(file,token, serviceLocator)}` - }) + }).code(200) } catch (e){ return handleError(e) diff --git a/test/unit/application/usecase/user/getAccessToken.test.js b/test/unit/application/usecase/user/getAccessToken.test.js index 35f6a7b..62a8835 100644 --- a/test/unit/application/usecase/user/getAccessToken.test.js +++ b/test/unit/application/usecase/user/getAccessToken.test.js @@ -18,9 +18,26 @@ describe('getAccessToken', () =>{ photo_temporaire:"path/to/file", password:await bcrypt.hash(passwordTest,10), token:"token", + is_private: false, id_role:1, ban_until:new Date("10-06-2003"), } + const expectedAccessToken = { + user: { + id_utilisateur:"id", + pseudo:"pseudo", + email:"email", + alias:"alias", + bio:"bio", + photo:"path/to/file", + photo_temporaire:"path/to/file", + type: 'user', + is_private: false, + id_role:1, + ban_until:new Date("10-06-2003"), + }, + token: 1 + } const mockUserRepository = new UserRepository(); const mockAccessTokenManager = {}; mockAccessTokenManager.generate = jest.fn((uid) => 1) @@ -34,7 +51,7 @@ describe('getAccessToken', () =>{ accessTokenManager: mockAccessTokenManager } ) - ).toBe(1) + ).toEqual(expectedAccessToken) }) From 02b54f14471dd3e575e5ce899ffa004959e56f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:55:20 +0100 Subject: [PATCH 44/67] corrected createdAt date for reviews (#44) --- lib/domain/model/Review.js | 3 +-- lib/domain/model/ReviewPublic.js | 2 +- test/integration/fixture/artist/getArtistFixture.js | 4 ++-- test/unit/application/usecase/artist/getArtistFixture.js | 6 ++++-- .../usecase/review/fixture/getReviewFixture.js | 8 ++++---- .../usecase/review/fixture/putReviewFixture.js | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/domain/model/Review.js b/lib/domain/model/Review.js index dd1712f..df207c4 100644 --- a/lib/domain/model/Review.js +++ b/lib/domain/model/Review.js @@ -1,13 +1,12 @@ module.exports = class { constructor(rawReview, utilisateur,type) { - this.id_review = rawReview.id_review this.id_oeuvre = rawReview.id_oeuvre this.countlikes = rawReview.dataValues.countLike this.countComment = rawReview.dataValues.countComment this.description = rawReview.description this.note = rawReview.note - this.createAt = rawReview.createdAt + this.createdAt = rawReview.createdAt this.updated_at = rawReview.updatedAt this.type = type diff --git a/lib/domain/model/ReviewPublic.js b/lib/domain/model/ReviewPublic.js index 693323e..2d5f7bc 100644 --- a/lib/domain/model/ReviewPublic.js +++ b/lib/domain/model/ReviewPublic.js @@ -6,7 +6,7 @@ module.exports = class { this.countComment = rawReview.countComment this.doesUserLike = doesUserLike this.note = rawReview.note - this.createAt = rawReview.created_at + this.createdAt = rawReview.createdAt this.oeuvre = oeuvre this.utilisateur = utilisateur this.type = rawReview.type diff --git a/test/integration/fixture/artist/getArtistFixture.js b/test/integration/fixture/artist/getArtistFixture.js index 6a8aac6..6cd7491 100644 --- a/test/integration/fixture/artist/getArtistFixture.js +++ b/test/integration/fixture/artist/getArtistFixture.js @@ -82,7 +82,7 @@ const mockLikedReview = { "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, - "createAt": "2024-01-03T00:00:00.000Z", + "createdAt": "2024-01-03T00:00:00.000Z", "updated_at": "2024-01-03T00:00:00.000Z", "type": "album", "utilisateur": { @@ -113,7 +113,7 @@ const mockCommentedReview = { "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, - "createAt": "2024-01-03T00:00:00.000Z", + "createdAt": "2024-01-03T00:00:00.000Z", "updated_at": "2024-01-03T00:00:00.000Z", "type": "album", "utilisateur": { diff --git a/test/unit/application/usecase/artist/getArtistFixture.js b/test/unit/application/usecase/artist/getArtistFixture.js index 4ef1190..bbda437 100644 --- a/test/unit/application/usecase/artist/getArtistFixture.js +++ b/test/unit/application/usecase/artist/getArtistFixture.js @@ -82,7 +82,7 @@ const mockLikedReview = { "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, - "createAt": "2024-01-03T00:00:00.000Z", + "createdAt": "2024-01-03T00:00:00.000Z", "updated_at": "2024-01-03T00:00:00.000Z", "type": "album", "utilisateur": { @@ -113,7 +113,7 @@ const mockCommentedReview = { "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, - "createAt": "2024-01-03T00:00:00.000Z", + "createdAt": "2024-01-03T00:00:00.000Z", "updated_at": "2024-01-03T00:00:00.000Z", "type": "album", "utilisateur": { @@ -263,6 +263,7 @@ const expectedArtist = { "countlikes": 32, "countComment": 0, "doesUserLike": true, + "createdAt": "2024-01-03T00:00:00.000Z", "note": 5, "oeuvre": { "id": "68YP0pEgwhnfRqQAzu71gP", @@ -322,6 +323,7 @@ const expectedArtist = { "id_review": 25, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "countlikes": 32, + "createdAt": "2024-01-03T00:00:00.000Z", "countComment": 0, "doesUserLike": true, "note": 5, diff --git a/test/unit/application/usecase/review/fixture/getReviewFixture.js b/test/unit/application/usecase/review/fixture/getReviewFixture.js index fc02ab2..2b09d51 100644 --- a/test/unit/application/usecase/review/fixture/getReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/getReviewFixture.js @@ -54,7 +54,7 @@ const rawReview = { countComment: 4, description: "C'est top", note: 5, - created_at: actualDate, + createdAt: actualDate, updated_at: actualDate, type: 'artist', utilisateur: mockUser @@ -65,7 +65,7 @@ const expectedReview = { countlikes: 2, countComment: 4, note: 5, - createAt: actualDate, + createdAt: actualDate, doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", @@ -89,7 +89,7 @@ const rawReviewPrivate = { countComment: 4, description: "C'est top", note: 5, - created_at: actualDate, + createdAt: actualDate, updated_at: actualDate, type: 'artist', utilisateur: mockUserPrivate @@ -100,7 +100,7 @@ const expectedPrivate = { countlikes: 2, countComment: 4, note: 5, - createAt: actualDate, + createdAt: actualDate, doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", diff --git a/test/unit/application/usecase/review/fixture/putReviewFixture.js b/test/unit/application/usecase/review/fixture/putReviewFixture.js index 7483044..177948a 100644 --- a/test/unit/application/usecase/review/fixture/putReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/putReviewFixture.js @@ -54,7 +54,7 @@ const rawReview = { countComment: 4, description: "C'est top", note: 5, - created_at: actualDate, + createdAt: actualDate, updated_at: actualDate, type: 'artist', utilisateur: mockUser @@ -65,7 +65,7 @@ const expectedReview = { countlikes: 2, countComment: 4, note: 5, - createAt: actualDate, + createdAt: actualDate, doesUserLike: false, oeuvre: { id: "32kWZXLpwGm5Y2B0lKb6Ii", From 73ee184d1c4d4ab274e3032e65f672afff0c6f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 19 Mar 2024 21:47:34 +0100 Subject: [PATCH 45/67] added getOeuvre review (#45) --- .../use_cases/review/getOeuvreReviews.js | 25 ++++++ .../use_cases/review/getUserReviews.js | 1 + .../fixture/review/getOeuvreReviewsFixture.js | 47 +++++++++++ test/integration/review.test.js | 9 +++ .../review/fixture/getOeuvreReviewsFixture.js | 80 +++++++++++++++++++ .../usecase/review/getOeuvreReviews.test.js | 40 ++++++++++ .../usecase/review/getUserReviews.test.js | 8 ++ 7 files changed, 210 insertions(+) create mode 100644 lib/application/use_cases/review/getOeuvreReviews.js create mode 100644 test/integration/fixture/review/getOeuvreReviewsFixture.js create mode 100644 test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js create mode 100644 test/unit/application/usecase/review/getOeuvreReviews.test.js diff --git a/lib/application/use_cases/review/getOeuvreReviews.js b/lib/application/use_cases/review/getOeuvreReviews.js new file mode 100644 index 0000000..f3e5705 --- /dev/null +++ b/lib/application/use_cases/review/getOeuvreReviews.js @@ -0,0 +1,25 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async (OeuvreId, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager}) => { + let id = null + if (userToken) { + id = accessTokenManager.decode(userToken)?.value + } + const reviews = await reviewRepository.getOeuvreReviews(page,pageSize, orderByLike,false,[OeuvreId],id) + const serializedReviews = [] + reviews.forEach(element => { + + serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) + }) + return await Promise.all(serializedReviews) +} + + +const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { + let doesUserLike = false + if(id_utilisateur) { + doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) + } + const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getUserReviews.js b/lib/application/use_cases/review/getUserReviews.js index 2bee394..062ddbd 100644 --- a/lib/application/use_cases/review/getUserReviews.js +++ b/lib/application/use_cases/review/getUserReviews.js @@ -2,6 +2,7 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); module.exports = async (pseudo, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,userRepository}) => { const testUsers = await userRepository.getByEmailOrPseudo(pseudo,pseudo) + if(!testUsers) throwStatusCode(404,"l'utilisateur n'existe pas") let id = null if (testUsers?.is_private) { let valid = false diff --git a/test/integration/fixture/review/getOeuvreReviewsFixture.js b/test/integration/fixture/review/getOeuvreReviewsFixture.js new file mode 100644 index 0000000..eba8825 --- /dev/null +++ b/test/integration/fixture/review/getOeuvreReviewsFixture.js @@ -0,0 +1,47 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + + +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updated_at: actualDate, + type: 'artist', + utilisateur: mockUser +} + + + + +module.exports = { + mockArtist, + rawReview, +} \ No newline at end of file diff --git a/test/integration/review.test.js b/test/integration/review.test.js index 75b29e6..d41e1dd 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -305,6 +305,15 @@ describe('review route', () => { }); expect(res1.statusCode).toBe(403); }) + + it("should return status code 404", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn().mockReturnValue(null) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews/user/{id}?page=1&pageSize=10&orderByLike=true`, + }); + expect(res1.statusCode).toBe(404); + }) }) describe("GET reviewLike route",()=>{ diff --git a/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js b/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js new file mode 100644 index 0000000..65d3606 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js @@ -0,0 +1,80 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_utilisateur: 1, + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updated_at: actualDate, + type: 'artist', + utilisateur: mockUser +} +const expectedReview = { + id_review:1, + description: "C'est top", + countlikes: 2, + countComment: 4, + note: 5, + createdAt: actualDate, + doesUserLike: false, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockPublicUser, + type: 'artist', +} + + + + + +module.exports = { + mockArtist, + rawReview, + expectedReview, +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/getOeuvreReviews.test.js b/test/unit/application/usecase/review/getOeuvreReviews.test.js new file mode 100644 index 0000000..ead5083 --- /dev/null +++ b/test/unit/application/usecase/review/getOeuvreReviews.test.js @@ -0,0 +1,40 @@ +const getOeuvreReviews = require("../../../../../lib/application/use_cases/review/getOeuvreReviews") +const catchError = require("../utils/catchError") +const { + mockArtist, + rawReview, + expectedReview, +} = require("./fixture/getOeuvreReviewsFixture") + +describe("getReviews Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + } + describe("successful cases", ()=>{ + it("should return serialized review with public user", async ()=>{ + mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const expectedReviews = [expectedReview] + const result = await getOeuvreReviews(1,undefined,1,10,true, serviceLocator) + expect(result).toEqual(expectedReviews) + }) + it("should return serialized review with private user", async ()=>{ + + mockAccesTokenManager.decode = jest.fn((userToken) => { + return {value: 1} + }) + mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + const expectedReviews = [expectedReview] + const result = await getOeuvreReviews(1,'token',1,10,true, serviceLocator) + expect(result).toEqual(expectedReviews) + }) + }) + +}) \ No newline at end of file diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js index b191e6b..0345ade 100644 --- a/test/unit/application/usecase/review/getUserReviews.test.js +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -66,6 +66,14 @@ describe("getReviews Test", ()=>{ }) expect(error.code).toBe(403) }) + + it("should throw error user dont exist", async ()=>{ + mockUserRepository.getByEmailOrPseudo = jest.fn().mockReturnValue(null) + const error = await catchError(async ()=>{ + await getUserReviews(1,undefined,1,10,true, serviceLocator) + }) + expect(error.code).toBe(404) + }) it("should throw error user private 2", async ()=>{ mockUserRepository.getByEmailOrPseudo = jest.fn((pseudo,email) => { return { From 07c6d34f62fca59cbd555f75aa54d0b89f86eeaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:39:40 +0100 Subject: [PATCH 46/67] Fix/get oeuvre fav (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ajout, supression et get oeuvres Fav, reste tests * Finalisation --------- Co-authored-by: Léa Thai --- .../use_cases/user/getOeuvresFav.js | 10 + lib/application/use_cases/user/oeuvreFav.js | 35 ++ lib/domain/entity/UserEntity.js | 9 +- lib/infrastructure/config/service-locator.js | 3 + .../repositories/OeuvreFavRepository.js | 69 ++++ .../interfaces/OeuvreFavRepositoryAbstract.js | 20 ++ lib/interfaces/controllers/UsersController.js | 34 ++ lib/interfaces/routes/users.js | 51 ++- test/integration/users.test.js | 333 ++++++++++++++++++ .../usecase/user/getOeuvreFav.test.js | 64 ++++ .../usecase/user/oeuvreFav.test.js | 223 ++++++++++++ 11 files changed, 849 insertions(+), 2 deletions(-) create mode 100644 lib/application/use_cases/user/getOeuvresFav.js create mode 100644 lib/application/use_cases/user/oeuvreFav.js create mode 100644 lib/infrastructure/repositories/OeuvreFavRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/OeuvreFavRepositoryAbstract.js create mode 100644 test/unit/application/usecase/user/getOeuvreFav.test.js create mode 100644 test/unit/application/usecase/user/oeuvreFav.test.js diff --git a/lib/application/use_cases/user/getOeuvresFav.js b/lib/application/use_cases/user/getOeuvresFav.js new file mode 100644 index 0000000..9c0d9b8 --- /dev/null +++ b/lib/application/use_cases/user/getOeuvresFav.js @@ -0,0 +1,10 @@ +const throwStatusCode = require("../utils/throwStatusCode"); + +module.exports = async (userToken, {userRepository, oeuvreFavRepository, accessTokenManager}) =>{ + const idUtilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(idUtilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + + let oeuvresFav = [] + oeuvresFav = await oeuvreFavRepository.getOeuvresFav(idUtilisateur) + return oeuvresFav +} \ No newline at end of file diff --git a/lib/application/use_cases/user/oeuvreFav.js b/lib/application/use_cases/user/oeuvreFav.js new file mode 100644 index 0000000..f6b9412 --- /dev/null +++ b/lib/application/use_cases/user/oeuvreFav.js @@ -0,0 +1,35 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const getAlbum = require("../spotify/getAlbum"); +const getTrack = require("../spotify/getTrack"); +const { error } = require("@hapi/joi/lib/base"); + +module.exports = async (userToken, idOeuvre, {userRepository, oeuvreFavRepository, accessTokenManager,spotifyRepository}) =>{ + const idUtilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(idUtilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + + //chercher album + // sinon cherche track + let oeuvre + + try { + oeuvre = await getAlbum(idOeuvre, {spotifyRepository}); + } catch (errorAlbum) { + + try { + oeuvre = await getTrack(idOeuvre, {spotifyRepository}); + } catch (errorTrack) { + throwStatusCode(404, "L'ID de l'oeuvre est introuvable"); + } + } + + if (!(await oeuvreFavRepository.oeuvreFavExists(idUtilisateur, idOeuvre))){ + + if (!(await oeuvreFavRepository.ajoutPossible(idUtilisateur))) throwStatusCode(403,"Vous avez atteints le nombre maximal d'oeuvres favorites") + + await oeuvreFavRepository.addOeuvrefav(idUtilisateur,idOeuvre) + return true + } + + await oeuvreFavRepository.deleteOeuvrefav(idUtilisateur,idOeuvre) + return false +} \ No newline at end of file diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index b549ee8..dffdb6a 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -47,6 +47,12 @@ const follow = Joi.object().keys({ .min(1) .required(), }) +const oeuvreFav = Joi.object().keys({ + idOeuvre: Joi + .string() + .min(1) + .required(), +}) module.exports = { userSignIn, @@ -58,5 +64,6 @@ module.exports = { sendResetEmail, resetPassword, follow, - authWithSpotify + authWithSpotify, + oeuvreFav } \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index 718b23e..59b6139 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -10,6 +10,8 @@ const FollowRepository= require('../repositories/FollowRepository'); const FriendRepository= require('../repositories/FriendRepository'); const ReviewRepository= require('../repositories/ReviewRepository'); const LikeOeuvreRepository= require('../repositories/LikeOeuvreRepository'); +const OeuvreFavRepository= require('../repositories/OeuvreFavRepository'); + function buildBeans() { return { @@ -20,6 +22,7 @@ function buildBeans() { mailRepository: new NodemailerRepository(process.env.MAILER_SERVICE,process.env.MAILER_EMAIL,process.env.MAILER_PASS), followRepository: new FollowRepository(), friendRepository: new FriendRepository(), + oeuvreFavRepository: new OeuvreFavRepository(), reviewRepository: new ReviewRepository(), likeOeuvreRepository: new LikeOeuvreRepository() }; diff --git a/lib/infrastructure/repositories/OeuvreFavRepository.js b/lib/infrastructure/repositories/OeuvreFavRepository.js new file mode 100644 index 0000000..1f7234f --- /dev/null +++ b/lib/infrastructure/repositories/OeuvreFavRepository.js @@ -0,0 +1,69 @@ +'use strict'; + + +const sequelize = require('../orm/sequelize/sequelize'); +const { Op } = require('sequelize'); +const { assert } = require('joi'); +const OeuvreFavRepositoryAbstract = require('./interfaces/OeuvreFavRepositoryAbstract'); + +module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract + + constructor() { + super(); + this.db = sequelize; + this.oeuvreFav = this.db.model('oeuvre_favorite'); + this.oeuvresFavMax = 3; + } + + async ajoutPossible(idUtilisateur){ + let nbOeuvresFav = await this.oeuvreFav.count({ + where: { + id_utilisateur: idUtilisateur + }, + }); + return nbOeuvresFav < this.oeuvresFavMax; + } + + async addOeuvrefav(idUtilisateur, idOeuvre) { + + const seqUser = await this.oeuvreFav.create({ + id_utilisateur: idUtilisateur, + id_oeuvre: idOeuvre, + createdAt: sequelize.literal('CURRENT_TIMESTAMP'), + updatedAt: sequelize.literal('CURRENT_TIMESTAMP') + }) + await seqUser.save() + } + + async deleteOeuvrefav(idUtilisateur, idOeuvre) { + const seqUser = await this.oeuvreFav.destroy({ + where: { + id_utilisateur: idUtilisateur, + id_oeuvre: idOeuvre + } + }) + } + + async oeuvreFavExists(idUtilisateur, idOeuvre) { + let oeuvre = await this.oeuvreFav.findOne({ + where: { + [Op.and]: { + id_utilisateur: idUtilisateur, + id_oeuvre: idOeuvre + } + } + }) + return !!oeuvre + } + + async getOeuvresFav(idUtilisateur) { + let oeuvresFav = await this.oeuvreFav.findAll({ + where: { + id_utilisateur: idUtilisateur + }, + }); + return oeuvresFav ? oeuvresFav.map(oeuvre => oeuvre.id_oeuvre) : null; + } + +}; + diff --git a/lib/infrastructure/repositories/interfaces/OeuvreFavRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/OeuvreFavRepositoryAbstract.js new file mode 100644 index 0000000..47a268b --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/OeuvreFavRepositoryAbstract.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = class { + + addOeuvrefav(idUtilisateur, idOeuvre) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + deleteOeuvrefav(idUtilisateur, idOeuvre) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + oeuvreFavExists(idUtilisateur, idOeuvre) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + getOeuvresFav(idUtilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } + ajoutPossible(idUtilisateur) { + throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + } +}; diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index 31b7c6f..c9606d6 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -14,6 +14,9 @@ const changePrivateStatus = require("../../application/use_cases/user/changePriv const refreshToken = require("../../application/use_cases/spotify/RefreshToken") const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); const follow = require("../../application/use_cases/user/follow"); +const oeuvreFav = require("../../application/use_cases/user/oeuvreFav"); +const getOeuvresFav = require("../../application/use_cases/user/getOeuvresFav"); + module.exports = { async confirmUser(request, handler) { @@ -74,6 +77,7 @@ module.exports = { const serviceLocator = request.server.app.serviceLocator; const {email, password} = request.payload try{ + console.log(email, password) const user = await CreateUser(email,password,serviceLocator); return handler.response("compte créé").code(200) @@ -153,5 +157,35 @@ module.exports = { catch (e){ return handleError(e) } + }, + async oeuvreFav(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {idOeuvre} = request.payload + try{ + // await oeuvreFav(idOeuvre,serviceLocator); + const returnValue = await oeuvreFav(token,idOeuvre,serviceLocator) + ? "L'oeuvre a été rajoutée en favori" + : "L'oeuvre a été retirée de vos favori" + return handler.response(returnValue).code(200) + } + catch (e){ + return handleError(e) + } + }, + + async getOeuvresFav(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + try{ + const arrayOeuvresFav = await getOeuvresFav(token,serviceLocator) + + return handler.response(arrayOeuvresFav).code(200) + } + catch (e){ + return handleError(e) + } } }; diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 439f321..8de4fc9 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -9,7 +9,8 @@ const { sendResetEmail, resetPassword, follow, - authWithSpotify + authWithSpotify, + oeuvreFav } = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); const MAX_BYTE_SIZE =20971520 @@ -280,6 +281,54 @@ module.exports = { }, } }, + { + method: 'POST', + path: '/users/oeuvreFav', + handler: UsersController.oeuvreFav, + options: { + description: 'add favorite music', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: oeuvreFav + } + } + }, + { + method: 'GET', + path: '/users/getOeuvresFav', + handler: UsersController.getOeuvresFav, + options: { + description: 'get favorites music', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 503: {description : 'Service unavailable'}, + } + } + } + } + }, + ]); } }; \ No newline at end of file diff --git a/test/integration/users.test.js b/test/integration/users.test.js index b6d30a8..c9b1e01 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -13,6 +13,7 @@ const mockSpotifyRepository = {} const mockMailRepository = {} const mockDocumentRepository = {} const mockFollowRepository = {} +const mockOeuvreFavRepository = {} const mockToken = jwt.sign({ sub: 'my-sub', // needs to match definition above value: 1, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user @@ -35,6 +36,7 @@ describe('user route', () => { mailRepository: mockMailRepository, documentRepository: mockDocumentRepository, followRepository: mockFollowRepository, + oeuvreFavRepository : mockOeuvreFavRepository, } server.register(Jwt) @@ -587,4 +589,335 @@ describe('user route', () => { expect(mockUserRepository.changePrivateStatus).toHaveBeenCalledTimes(1); }) }) + + describe("/users/oeuvreFav", ()=> { + afterEach(()=>{ + jest.clearAllMocks(); + }) + + const { albumRawOneArtist } = require("../unit/interfaces/serializers/fixtures/albumFixture.js") + const { rawTrackWithOneArtist } = require("../unit/interfaces/serializers/fixtures/albumTrackFixture.js") + + // login erreur + it("should return error code 401",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => null) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(401); + }) + + // payload incorrect avec entity + it("should return error code 400",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: null, + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(400); + }) + + // payload incorrect avec entity + it("should return error code 400",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: 123, // doit être un string + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(400); + }) + // aucune oeuvre de trouve + it("should return error code 404",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return {error : {status: 400, message: "invalid id"}} + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return {error : {status: 400, message: "invalid id"}} + }) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test1323", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(404); + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1); + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1); + + }) + + // plus de 3 oeuvres favorites avec album + it("should return error code 403",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", // doit être un string + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(403) + }) + + // plus de 3 oeuvres favorites avec track + it("should return error code 403",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return {error : {status: 400, message: "invalid id"}} + }) + + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(403) + }) + + // ajout réussie avec album + it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(200) +}) + +// ajout réussie avec track +it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return {error : {status: 400, message: "invalid id"}} + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(200) +}) + + // supression reussite avec album + it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.ajoutPossible = jest.fn() + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() + + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(200) +}) + + // supression reussite avec track +it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return {error : {status: 400, message: "invalid id"}} + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.ajoutPossible = jest.fn() + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() + + const res = await server.inject({ + method: 'POST', + url: '/users/oeuvreFav', + payload: { + idOeuvre: "test123", + }, + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled() + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) + expect(res.statusCode).toBe(200) + }) + +}) + + + describe("/users/getOeuvresFav", ()=> { + afterEach(()=>{ + jest.clearAllMocks(); + }) + it("should return error code 401",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => null) + + const res = await server.inject({ + method: 'GET', + url: '/users/getOeuvresFav', + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(401); + }) + }) + it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { + return [1,2,3] + }) + const res = await server.inject({ + method: 'GET', + url: '/users/getOeuvresFav', + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(200); + }) + + it("should return code 200",async ()=>{ + mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) + mockUserRepository.getByUser = jest.fn(() => 1) + mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { + return [] + }) + const res = await server.inject({ + method: 'GET', + url: '/users/getOeuvresFav', + headers: { + Authorization: `Bearer ${mockToken}` + } + }) + expect(res.statusCode).toBe(200); + }) + }); + + + + diff --git a/test/unit/application/usecase/user/getOeuvreFav.test.js b/test/unit/application/usecase/user/getOeuvreFav.test.js new file mode 100644 index 0000000..46150b2 --- /dev/null +++ b/test/unit/application/usecase/user/getOeuvreFav.test.js @@ -0,0 +1,64 @@ +const getOeuvresFav = require("../../../../../lib/application/use_cases/user/getOeuvresFav.js") +const throwStatusCode = require("../../../../../lib/application/use_cases/utils/throwStatusCode.js") +const catchError = require("../utils/catchError.js") + +describe("getOeuvresFav Test", () => { + const userToken = 'token' + + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const mockUserRepository = {} + const mockOeuvreFavRepository = {} + const serviceLocator = { + userRepository: mockUserRepository, + oeuvreFavRepository: mockOeuvreFavRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + } + + describe("invalid and valid cases", () => { + + it("should throw error bad auth token", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => null) + const error = await catchError(async () => { + await getOeuvresFav(userToken, serviceLocator) + }) + expect(error.code).toBe(401) + }) + + it("should send an array with oeuvre fav id", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockOeuvreFavRepository.getOeuvresFav = jest.fn((userToken) => { + return [1, 2, 3] + }) + + const result = await getOeuvresFav(userToken, serviceLocator) + + expect(result).toEqual([1, 2, 3]) + expect(mockOeuvreFavRepository.getOeuvresFav).toHaveBeenCalledTimes(1) + }) + + it("should send a empty array", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockOeuvreFavRepository.getOeuvresFav = jest.fn((userToken) => { + return [] + }) + + const result = await getOeuvresFav(userToken, serviceLocator) + + expect(result).toEqual([]) + expect(mockOeuvreFavRepository.getOeuvresFav).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/test/unit/application/usecase/user/oeuvreFav.test.js b/test/unit/application/usecase/user/oeuvreFav.test.js new file mode 100644 index 0000000..cb5b4bf --- /dev/null +++ b/test/unit/application/usecase/user/oeuvreFav.test.js @@ -0,0 +1,223 @@ +const OeuvreFav = require("../../../../../lib/application/use_cases/user/oeuvreFav.js") +const throwStatusCode = require("../../../../../lib/application/use_cases/utils/throwStatusCode.js") +const catchError = require("../utils/catchError.js") +const { albumRawOneArtist } = require("../../../interfaces/serializers/fixtures/albumFixture.js") +const { rawTrackWithOneArtist } = require("../../../interfaces/serializers/fixtures/albumTrackFixture.js") + +describe("OeuvreFav Test", () => { + const idOeuvre = 'idOeuvre' + const userToken = 'token' + + const mockAccesTokenManager = {} + const mockSpotifyRepository = {} + const mockUserRepository = {} + const mockOeuvreFavRepository = {} + const serviceLocator = { + userRepository: mockUserRepository, + oeuvreFavRepository: mockOeuvreFavRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + } + + describe("invalid and valid cases", () => { + + it("should throw error bad auth token", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => null) + const error = await catchError(async () => { + await OeuvreFav(userToken, idOeuvre, serviceLocator) + }) + expect(error.code).toBe(401) + }) + + // aucune oeuvre de trouve + it("should throw incrorrect id oeuvre error", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + throwStatusCode("400", "invalid id"); + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + throwStatusCode("400", "invalid id"); + }) + const error = await catchError(async () => { + await OeuvreFav(userToken, idOeuvre, serviceLocator) + }) + expect(error.code).toBe(404) + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + }) + + // plus de 3 oeuvres favorites avec album + it("should throw maximal add", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) + + const error = await catchError(async () => { + await OeuvreFav(userToken, idOeuvre, serviceLocator) + }) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(error.code).toBe(403) + }) + + // plus de 3 oeuvres favorites avec track + it("should throw maximal add", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + throwStatusCode("400", "invalid id"); + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) + + const error = await catchError(async () => { + await OeuvreFav(userToken, idOeuvre, serviceLocator) + }) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(error.code).toBe(403) + }) + + // ajout réussie avec album + it("should put an album", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + + const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) + expect(result).toEqual(true) + }) + + // ajout réussie avec track + it("should put an track", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + throwStatusCode("400", "invalid id"); + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) + mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + + const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) + expect(result).toEqual(true) + }) + + + + // supression réussite d'un album + it("should remove an album ", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.ajoutPossible = jest.fn() + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() + + const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) + expect(result).toEqual(false) + }) + + + // supression réussite d'une track + it("should remove a track ", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1 + } + }) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + throwStatusCode("400", "invalid id"); + }) + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) + mockOeuvreFavRepository.ajoutPossible = jest.fn() + mockOeuvreFavRepository.addOeuvrefav = jest.fn() + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() + + const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) + + expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) + expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled() + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) + expect(result).toEqual(false) + }) + + }) +}) From f1b6982005515dbbbfc036d3ff97e812fa3119ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Mon, 25 Mar 2024 17:32:32 +0100 Subject: [PATCH 47/67] Fix/get oeuvre reviews (#47) * added mission functionnality * added missing file --- .../use_cases/review/getOeuvreReviews.js | 1 + lib/domain/entity/ReviewEntity.js | 16 ++++- .../controllers/ReviewController.js | 14 +++++ lib/interfaces/routes/review.js | 31 +++++++++- .../fixture/review/getOeuvreReviewFixture.js | 60 +++++++++++++++++++ test/integration/review.test.js | 34 +++++++++++ .../usecase/review/getOeuvreReviews.test.js | 21 ++++++- 7 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 test/integration/fixture/review/getOeuvreReviewFixture.js diff --git a/lib/application/use_cases/review/getOeuvreReviews.js b/lib/application/use_cases/review/getOeuvreReviews.js index f3e5705..230fb00 100644 --- a/lib/application/use_cases/review/getOeuvreReviews.js +++ b/lib/application/use_cases/review/getOeuvreReviews.js @@ -21,5 +21,6 @@ const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,review doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) } const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) } \ No newline at end of file diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index cc3e3e4..6836c48 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -62,7 +62,17 @@ const userReviewParams = Joi.object().keys({ const userReviewQuery = Joi.object().keys({ page: Joi.number().integer().min(1).required(), pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean().invalid(false) + orderByLike: Joi.boolean() +}) + +const oeuvreReviewParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) + +const oeuvreReviewQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean() }) module.exports = { putReview, @@ -72,5 +82,7 @@ module.exports = { likeReviewParams, likeReviewQuery, userReviewParams, - userReviewQuery + userReviewQuery, + oeuvreReviewParams, + oeuvreReviewQuery } \ No newline at end of file diff --git a/lib/interfaces/controllers/ReviewController.js b/lib/interfaces/controllers/ReviewController.js index 20422c6..d5c6003 100644 --- a/lib/interfaces/controllers/ReviewController.js +++ b/lib/interfaces/controllers/ReviewController.js @@ -5,6 +5,7 @@ const getReviews = require("../../application/use_cases/review/getReviews"); const likeReview = require("../../application/use_cases/review/likeReview"); const getReviewLikes = require("../../application/use_cases/review/getReviewLikes"); const getUserReviews = require("../../application/use_cases/review/getUserReviews"); +const getOeuvreReviews = require("../../application/use_cases/review/getOeuvreReviews"); const handleError = require("./utils/handleError"); module.exports = { @@ -98,4 +99,17 @@ module.exports = { return handleError(error) } }, + async getOeuvreReviews(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {page, pageSize,orderByLike} = request.query + return handler.response(await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, } \ No newline at end of file diff --git a/lib/interfaces/routes/review.js b/lib/interfaces/routes/review.js index 38bdd65..97a9301 100644 --- a/lib/interfaces/routes/review.js +++ b/lib/interfaces/routes/review.js @@ -7,7 +7,9 @@ const { likeReviewParams, likeReviewQuery, userReviewParams, - userReviewQuery + userReviewQuery, + oeuvreReviewParams, + oeuvreReviewQuery } = require("../../domain/entity/ReviewEntity"); module.exports = { name: 'review', @@ -200,6 +202,33 @@ module.exports = { query: userReviewQuery } } + }, + { + method: 'GET', + path: '/reviews/oeuvre/{id}', + handler: ReviewController.getOeuvreReviews, + options: { + description: 'get oeuvre reviews', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: oeuvreReviewParams, + query: oeuvreReviewQuery + } + } } //GET USER LIKES MAYBE ]); diff --git a/test/integration/fixture/review/getOeuvreReviewFixture.js b/test/integration/fixture/review/getOeuvreReviewFixture.js new file mode 100644 index 0000000..ae20643 --- /dev/null +++ b/test/integration/fixture/review/getOeuvreReviewFixture.js @@ -0,0 +1,60 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_utilisateur: 1, + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlikes: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updated_at: actualDate, + type: 'artist', + utilisateur: mockUser +} + + + + + + +module.exports = { + mockArtist, + rawReview, +} \ No newline at end of file diff --git a/test/integration/review.test.js b/test/integration/review.test.js index d41e1dd..5fd7826 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -488,4 +488,38 @@ describe('review route', () => { expect(res1.statusCode).toBe(403); }) }) + + describe("GET oeuvreReviews",()=>{ + const { + mockArtist, + rawReview, + } = require("./fixture/review/getOeuvreReviewFixture.js") + it("should return status code 200", async ()=>{ + mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews/oeuvre/{id}?page=1&pageSize=10&orderByLike=true`, + }); + expect(res1.statusCode).toBe(200); + }) + + it("should return status code 400", async ()=>{ + mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const res1 = await server.inject({ + method: 'GET', + url: `/reviews/oeuvre/{id}?page=1&pageSize=10&orderByLike=true`, + }); + expect(res1.statusCode).toBe(400); + }) + + }) }); diff --git a/test/unit/application/usecase/review/getOeuvreReviews.test.js b/test/unit/application/usecase/review/getOeuvreReviews.test.js index ead5083..65c1639 100644 --- a/test/unit/application/usecase/review/getOeuvreReviews.test.js +++ b/test/unit/application/usecase/review/getOeuvreReviews.test.js @@ -15,7 +15,7 @@ describe("getReviews Test", ()=>{ accessTokenManager: mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, } - describe("successful cases", ()=>{ + describe("valid cases", ()=>{ it("should return serialized review with public user", async ()=>{ mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) @@ -36,5 +36,24 @@ describe("getReviews Test", ()=>{ expect(result).toEqual(expectedReviews) }) }) + + describe("invalide cases", ()=>{ + it("should throw error 400", async ()=>{ + mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) + mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const error = await catchError(async () => { + await getOeuvreReviews(1,undefined,1,10,true, serviceLocator) + }) + expect(error.code).toBe(400) + }) + + }) }) \ No newline at end of file From f7aedddf7434a3230b35853d52affda37f883a10 Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Tue, 26 Mar 2024 15:44:16 +0100 Subject: [PATCH 48/67] correction des erreurs + ajout de review count pour les oeuvres --- .../use_cases/review/getCountOeuvreReviews.js | 12 ++++++++++++ lib/interfaces/serializers/ArtistSerializer.js | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 lib/application/use_cases/review/getCountOeuvreReviews.js diff --git a/lib/application/use_cases/review/getCountOeuvreReviews.js b/lib/application/use_cases/review/getCountOeuvreReviews.js new file mode 100644 index 0000000..e5b15d1 --- /dev/null +++ b/lib/application/use_cases/review/getCountOeuvreReviews.js @@ -0,0 +1,12 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (OeuvreId, userToken, {reviewRepository, userRepository, accessTokenManager}) => { + let id = null + if (userToken) { + id = accessTokenManager.decode(userToken)?.value + } + if(! await userRepository.getByUser(id)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + + const count = await reviewRepository.getReviewCount(OeuvreId) + + return count +} diff --git a/lib/interfaces/serializers/ArtistSerializer.js b/lib/interfaces/serializers/ArtistSerializer.js index 249b1ac..15fde98 100644 --- a/lib/interfaces/serializers/ArtistSerializer.js +++ b/lib/interfaces/serializers/ArtistSerializer.js @@ -4,7 +4,7 @@ const serializeArtiste = (artisteRaw) => { id: artisteRaw.id, name: artisteRaw.name, follower_count: artisteRaw?.follower_count, - image: artisteRaw?.images ? artisteRaw.images[0].url : null, + image: artisteRaw?.images && artisteRaw.images.length > 0 ? artisteRaw.images[0].url : null, spotify_url : artisteRaw?.external_urls?.spotify, popularity: artisteRaw?.popularity, genres : artisteRaw?.genres From 0415fcc2bcb184fed5ea10e07553bdf0753bc9ee Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Tue, 26 Mar 2024 15:45:41 +0100 Subject: [PATCH 49/67] correction des erreurs + ajout de review count pour les oeuvres --- lib/interfaces/controllers/ReviewController.js | 7 ++++++- lib/interfaces/serializers/AlbumSerializer.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/interfaces/controllers/ReviewController.js b/lib/interfaces/controllers/ReviewController.js index d5c6003..44a7abd 100644 --- a/lib/interfaces/controllers/ReviewController.js +++ b/lib/interfaces/controllers/ReviewController.js @@ -6,6 +6,7 @@ const likeReview = require("../../application/use_cases/review/likeReview"); const getReviewLikes = require("../../application/use_cases/review/getReviewLikes"); const getUserReviews = require("../../application/use_cases/review/getUserReviews"); const getOeuvreReviews = require("../../application/use_cases/review/getOeuvreReviews"); +const getCountOeuvreReviews = require("../../application/use_cases/review/getCountOeuvreReviews"); const handleError = require("./utils/handleError"); module.exports = { @@ -107,7 +108,11 @@ module.exports = { const token = authorizationHeader?.split(' ')[1]; const {id} = request.params const {page, pageSize,orderByLike} = request.query - return handler.response(await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) + const response = { + data: await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator), + count: await getCountOeuvreReviews(id,token, serviceLocator) + }; + return handler.response(response).code(200) }catch(error){ return handleError(error) } diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index a1686e7..5271134 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -14,9 +14,9 @@ const serializeAlbum = (albumRaw) => { artists : albumRaw?.artists?.map(item => SerializeArtist(item)), tracks: tracks, genres : albumRaw?.genres, - type : albumRaw?.album_group ? albumRaw?.album_group : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) rating: albumRaw?.rating, reviewCount: albumRaw?.reviewCount, + type : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), }; return new Album(album) From e4adf14d7c3916566e3d17929b23cbf5fc5dbf4a Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Sun, 7 Apr 2024 19:05:22 +0200 Subject: [PATCH 50/67] =?UTF-8?q?Resolution=20du=20probleme=20de=20confide?= =?UTF-8?q?ntialit=C3=A9=20dans=20friend=20repos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/infrastructure/repositories/FriendRepository.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index 598a5cf..037789e 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -2,7 +2,7 @@ const sequelize = require('../orm/sequelize/sequelize'); const Friend = require('../../domain/model/Friend'); -const User = require('../../domain/model/User'); +const User = require('../../domain/model/UserPublic'); const FriendRepositoryAbstract = require('./interfaces/FriendRepositoryAbstract'); const { Op } = require('sequelize'); From d100f210b619eb993df384dbc81d1d27abaea7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:23:28 +0200 Subject: [PATCH 51/67] page oeuvre (#48) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * page oeuvre * Resolution du probleme de confidentialité dans friend repos * page oeuvre * correction erreur 1 * reste finalisation tests --------- Co-authored-by: yousrah24 --- lib/application/use_cases/oeuvre/getOeuvre.js | 64 +++ lib/domain/entity/OeuvreEntity.js | 7 +- lib/domain/model/Album.js | 2 + lib/domain/model/OeuvrePage.js | 10 + lib/domain/model/Track.js | 3 + .../repositories/LikeOeuvreRepository.js | 2 +- .../repositories/ReviewRepository.js | 9 +- .../controllers/OeuvreController.js | 14 + lib/interfaces/routes/oeuvre.js | 35 +- lib/interfaces/serializers/AlbumSerializer.js | 1 + lib/interfaces/serializers/TrackSerializer.js | 3 + .../usecase/fixtures/getOeuvreFixture.js | 394 ++++++++++++++++++ .../usecase/oeuvre/getOeuvre.test.js | 148 +++++++ 13 files changed, 685 insertions(+), 7 deletions(-) create mode 100644 lib/application/use_cases/oeuvre/getOeuvre.js create mode 100644 lib/domain/model/OeuvrePage.js create mode 100644 test/unit/application/usecase/fixtures/getOeuvreFixture.js create mode 100644 test/unit/application/usecase/oeuvre/getOeuvre.test.js diff --git a/lib/application/use_cases/oeuvre/getOeuvre.js b/lib/application/use_cases/oeuvre/getOeuvre.js new file mode 100644 index 0000000..97346a5 --- /dev/null +++ b/lib/application/use_cases/oeuvre/getOeuvre.js @@ -0,0 +1,64 @@ +const throwStatusCode = require("../utils/throwStatusCode.js"); +const OeuvrePage = require("../../../domain/model/OeuvrePage.js"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer.js"); +const fetchArtist = require("../spotify/FetchArtist.js"); +const getAlbum = require("../spotify/getAlbum.js"); +const getTrack = require("../spotify/getTrack.js"); + + +module.exports = async (idOeuvre, userToken, {accessTokenManager,userRepository, spotifyRepository, reviewRepository,likeOeuvreRepository, oeuvreFavRepository}) => { + const idUtilisateur = accessTokenManager.decode(userToken)?.value + const user = await userRepository.getByUser(idUtilisateur) + if(!user) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + + let oeuvre + + try { + oeuvre = await getAlbum(idOeuvre, {spotifyRepository}); + } catch (errorAlbum) { + try { + oeuvre = await getTrack(idOeuvre, {spotifyRepository}); + } catch (errorTrack) { + + throwStatusCode(404, "L'ID de l'oeuvre est introuvable"); + } + } + + const artistIds = oeuvre.artists.map((artist)=>artist.id) + + const artists = await Promise.all(artistIds.map(async (id) => { + return await fetchArtist(id, { spotifyRepository }); + })); + + const doesUserLikes = await likeOeuvreRepository.doesUserLikes(idUtilisateur,idOeuvre) + const doesUserFav = await oeuvreFavRepository.oeuvreFavExists(idUtilisateur,idOeuvre) + + oeuvre.likeCount = await likeOeuvreRepository.getLikeCount(idOeuvre) + oeuvre.reviewCount = await reviewRepository.getReviewCount(idOeuvre) + oeuvre.rating = await reviewRepository.getOeuvreRating(idOeuvre) + + const reviewsByLike = await reviewRepository.getOeuvreReviews(1,3,true,false,idOeuvre,idUtilisateur) + const reviewsByTime = await reviewRepository.getOeuvreReviews(1,3,false,false,idOeuvre,idUtilisateur) + + const reviewsByLikeSeri = await Promise.all( + reviewsByLike.map(async (review) => { + const doesUserLikeReview = await reviewRepository.doesUserLike(idUtilisateur,review.id_review) + const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLikeReview) + }) + ) + + const reviewsByTimeSeri = await Promise.all( + reviewsByTime.map(async (review) => { + const doesUserLikeReview = await reviewRepository.doesUserLike(idUtilisateur,review.id_review) + const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) + if(rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLikeReview) + }) + ) + + return new OeuvrePage(oeuvre, artists, reviewsByLikeSeri, reviewsByTimeSeri, doesUserLikes, doesUserFav) +} \ No newline at end of file diff --git a/lib/domain/entity/OeuvreEntity.js b/lib/domain/entity/OeuvreEntity.js index 1e4fb1f..4c50b07 100644 --- a/lib/domain/entity/OeuvreEntity.js +++ b/lib/domain/entity/OeuvreEntity.js @@ -11,6 +11,11 @@ const likeOeuvre = Joi.object().keys({ id: Joi.string().max(50).required() }) +const getOeuvre = Joi.object().keys({ + id: Joi.string().min(1).required() +}) + module.exports = { - likeOeuvre + likeOeuvre, + getOeuvre } \ No newline at end of file diff --git a/lib/domain/model/Album.js b/lib/domain/model/Album.js index f30fa3c..94ea97a 100644 --- a/lib/domain/model/Album.js +++ b/lib/domain/model/Album.js @@ -13,5 +13,7 @@ module.exports = class { this.tracks = album.tracks this.genres = album.genres this.type = album.type + this.likeCount = album.likeCount + } } \ No newline at end of file diff --git a/lib/domain/model/OeuvrePage.js b/lib/domain/model/OeuvrePage.js new file mode 100644 index 0000000..6c67011 --- /dev/null +++ b/lib/domain/model/OeuvrePage.js @@ -0,0 +1,10 @@ +module.exports = class { + constructor(oeuvre, artist, reviewsByLike, reviewsByTime, doesUserLikes, doesUserFav){ + this.oeuvre = oeuvre + this.artist = artist + this.reviewsByLike = reviewsByLike + this.reviewsByTime = reviewsByTime + this.doesUserLikes = doesUserLikes + this.doesUserFav = doesUserFav + } +} \ No newline at end of file diff --git a/lib/domain/model/Track.js b/lib/domain/model/Track.js index d37127d..56eb162 100644 --- a/lib/domain/model/Track.js +++ b/lib/domain/model/Track.js @@ -7,6 +7,9 @@ module.exports = class { this.duration_ms = track.duration_ms this.popularity = track.popularity this.spotify_url = track.spotify_url + this.rating = track.rating + this.reviewCount = track.reviewCount + this.likeCount = track.likeCount this.type = "track" } diff --git a/lib/infrastructure/repositories/LikeOeuvreRepository.js b/lib/infrastructure/repositories/LikeOeuvreRepository.js index 4e6597f..e9c4167 100644 --- a/lib/infrastructure/repositories/LikeOeuvreRepository.js +++ b/lib/infrastructure/repositories/LikeOeuvreRepository.js @@ -34,7 +34,7 @@ module.exports = class extends LikeOeuvreRepositoryAbstract { }) } getLikeCount(oeuvreId) { - return this.likeOeuvre.cout({ + return this.likeOeuvre.count({ where: { id_oeuvre: oeuvreId, } diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js index ffaf995..24237ed 100644 --- a/lib/infrastructure/repositories/ReviewRepository.js +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -142,16 +142,21 @@ module.exports = class extends ReviewRepository { async getOeuvreReviews(page,pageSize,orderByLike, fetchPrivate,ids_oeuvre, id) { + console.log("TEST") const orderColumn = orderByLike ? "countLike" : "createdAt"; const order = [[sequelize.literal(orderColumn), "DESC"]]; const offset = (page - 1) * pageSize; - let whereClause = {id_oeuvre: {[Op.in]: ids_oeuvre}} + let whereClause + if (Array.isArray(ids_oeuvre) && ids_oeuvre.length > 0) { + whereClause = { id_oeuvre: { [Op.in]: ids_oeuvre } }; + } else { + whereClause = { id_oeuvre: ids_oeuvre}; + } if(!fetchPrivate) { whereClause.id_utilisateur = id ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} } - const reviews = await this.review.findAll({ limit: pageSize, offset: offset, diff --git a/lib/interfaces/controllers/OeuvreController.js b/lib/interfaces/controllers/OeuvreController.js index e7f3b7b..7cde952 100644 --- a/lib/interfaces/controllers/OeuvreController.js +++ b/lib/interfaces/controllers/OeuvreController.js @@ -1,8 +1,10 @@ 'use strict'; const likeOeuvre = require('../../application/use_cases/oeuvre/likeOeuvre'); +const getOeuvre = require('../../application/use_cases/oeuvre/getOeuvre'); const handleError = require("./utils/handleError"); + module.exports = { async like(request, handler){ @@ -18,6 +20,18 @@ module.exports = { return handleError(e) } }, + async get(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {id} = request.params + try{ + return handler.response(await getOeuvre(id,token, serviceLocator)).code(200) + } + catch (e){ + return handleError(e) + } + } } diff --git a/lib/interfaces/routes/oeuvre.js b/lib/interfaces/routes/oeuvre.js index 2a202a9..ff39926 100644 --- a/lib/interfaces/routes/oeuvre.js +++ b/lib/interfaces/routes/oeuvre.js @@ -1,6 +1,8 @@ -const ArtistController = require("../controllers/OeuvreController.js"); +const OeuvreController = require("../controllers/OeuvreController.js"); + const { - likeOeuvre + likeOeuvre, + getOeuvre } = require("../../domain/entity/OeuvreEntity.js"); module.exports = { name: 'oeuvre', @@ -10,7 +12,7 @@ module.exports = { { method: 'POST', path: '/oeuvre/{type}/{id}/like', - handler: ArtistController.like, + handler: OeuvreController.like, options: { auth: 'jwt', description: 'like/unlike an oeuvre', @@ -34,6 +36,33 @@ module.exports = { } }, }, + { + method: 'GET', + path: '/oeuvre/{id}', + handler: OeuvreController.get, + options: { + auth: 'jwt', + description: 'get oeuvre information', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: getOeuvre + } + }, + } ]); } }; \ No newline at end of file diff --git a/lib/interfaces/serializers/AlbumSerializer.js b/lib/interfaces/serializers/AlbumSerializer.js index 5271134..277161e 100644 --- a/lib/interfaces/serializers/AlbumSerializer.js +++ b/lib/interfaces/serializers/AlbumSerializer.js @@ -17,6 +17,7 @@ const serializeAlbum = (albumRaw) => { rating: albumRaw?.rating, reviewCount: albumRaw?.reviewCount, type : albumRaw?.album_type, // utilise album_group (plus precis pour fetchArtistSongs pour recuperer appears_on) sinon album_type (present dans fetchAlbum et fetchArtistSongs, n'a pas appears_on) + likeCount: albumRaw?.likeCount, popularity: albumRaw.popularity ? albumRaw.popularity : Math.floor(Math.random() * 100), }; return new Album(album) diff --git a/lib/interfaces/serializers/TrackSerializer.js b/lib/interfaces/serializers/TrackSerializer.js index ced908e..88007a2 100644 --- a/lib/interfaces/serializers/TrackSerializer.js +++ b/lib/interfaces/serializers/TrackSerializer.js @@ -13,6 +13,9 @@ const serializeTrack = (trackRaw) => { spotify_url : trackRaw.external_urls.spotify, duration_ms: trackRaw?.duration_ms, popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, + rating: trackRaw?.rating, + likeCount: trackRaw?.likeCount, + reviewCount: trackRaw?.reviewCount, }; return new Track(track) diff --git a/test/unit/application/usecase/fixtures/getOeuvreFixture.js b/test/unit/application/usecase/fixtures/getOeuvreFixture.js new file mode 100644 index 0000000..bbda437 --- /dev/null +++ b/test/unit/application/usecase/fixtures/getOeuvreFixture.js @@ -0,0 +1,394 @@ +const mockArtist = { + external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, + genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], + id: '4FpJcNgOvIpSBeJgRg3OfN', + images: [ + { + height: 640, + url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', + width: 640 + }, + ], + name: 'Orelsan', + popularity: 64, + type: 'artist', + } +const mockAlbumRaw = { + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", + "items": [ + { + "album_group": "album", + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "release_date": "2022-10-28", + "total_tracks": 25, + "type": "album" + } + ] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const mockUserPrivate = { + pseudo: "John Doe2", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() + + +const mockLikedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createdAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockCommentedReview = { + "id_review": 25, + "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", + "countlikes": 32, + "countComment": 0, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "note": 5, + "createdAt": "2024-01-03T00:00:00.000Z", + "updated_at": "2024-01-03T00:00:00.000Z", + "type": "album", + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "token": null, + "refresh_token": null, + "reset_token": null, + "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + "id_role": 1, + "ban_until": null, + "confirmed": true, + "confirm_token": null, + "auth_with_spotify": false, + "is_private": true, + "type": "user" + } +} + +const mockOeuvreReviewSpotify = { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist" + } + ], + + "external_urls": { + "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + }, + "genres": [], + "id": "68YP0pEgwhnfRqQAzu71gP", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "width": 640 + } + ], + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "tracks": { + "href": "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", + "items": [ + { + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + }, + "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "type": "artist", + "uri": "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN" + } + ], + + "disc_number": 1, + "duration_ms": 161732, + "external_urls": { + "spotify": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4" + }, + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "preview_url": "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", + "track_number": 1, + "type": "track" + } + ], + "total": 25 + }, + "type": "album" +} + + +const expectedArtist = { + "artist": { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", + "popularity": 64, + "follower_count": 100, + "genres": [ + "french hip hop", + "old school rap francais", + "rap conscient" + ], + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + }, + "albums": [ + { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 64, + "rating": 1, + "reviewCount": 2, + "release_date": "2022-10-28", + "total_tracks": 25, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "type": "album" + } + ], + "friends_followers": { + "count": 1, + "users": [ + { + "id_utilisateur": 1, + "pseudo": "John Doe2", + "email": "testemail@gmail", + "alias": "John", + "photo": null, + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + } + ] + }, + "reviewsByLike": [ + { + "id_review": 25, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "countlikes": 32, + "countComment": 0, + "doesUserLike": true, + "createdAt": "2024-01-03T00:00:00.000Z", + "note": 5, + "oeuvre": { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "tracks": [ + { + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "duration_ms": 161732, + "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + "type": "track" + } + ], + "genres": [], + "type": "album" + }, + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + }, + "type": "album" + } + ], + "reviewsByTime": [ + { + "id_review": 25, + "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + "countlikes": 32, + "createdAt": "2024-01-03T00:00:00.000Z", + "countComment": 0, + "doesUserLike": true, + "note": 5, + "oeuvre": { + "id": "68YP0pEgwhnfRqQAzu71gP", + "name": "Civilisation Edition Ultime", + "popularity": 60, + "release_date": "2022-10-28", + "total_tracks": 1, + "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "tracks": [ + { + "id": "7b3YQboXo3kau9YVUyx3r4", + "name": "CP_001_ Intro Civilisation Perdue", + "artists": [ + { + "id": "4FpJcNgOvIpSBeJgRg3OfN", + "name": "Orelsan", + "image": null, + "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + "type": "artist" + } + ], + "duration_ms": 161732, + "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + "type": "track" + } + ], + "genres": [], + "type": "album" + }, + "utilisateur": { + "id_utilisateur": 54, + "pseudo": "Constance", + "email": "Constance.Constance@gmail.com", + "alias": "Constance", + "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + "photo_temporaire": null, + "id_role": 1, + "ban_until": null, + "is_private": true, + "type": "user" + }, + "type": "album" + } + ], + "doesUserFollow": true +} +module.exports = { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, + expectedArtist +} \ No newline at end of file diff --git a/test/unit/application/usecase/oeuvre/getOeuvre.test.js b/test/unit/application/usecase/oeuvre/getOeuvre.test.js new file mode 100644 index 0000000..8bc9531 --- /dev/null +++ b/test/unit/application/usecase/oeuvre/getOeuvre.test.js @@ -0,0 +1,148 @@ +const getOeuvre = require("./../../../../../lib/application/use_cases/oeuvre/getOeuvre") +const catchError = require("../utils/catchError") +const { + mockUser, + mockArtist, + mockUserPrivate, + mockAlbumRaw, + mockLikedReview, + mockCommentedReview, + mockOeuvreReviewSpotify, + expectedArtist +} = require('../fixtures/getOeuvreFixture') + +describe("getOeuvre Test", ()=>{ + const mockReviewRepository = {} + const mockAccesTokenManager = {} + const mockUserRepository = {} + const mockSpotifyRepository = {} + //const mockFollowRepository = {} + const mocklikeOeuvreRepository = {} + const mockoeuvreFavRepository = {} + + const serviceLocator = { + reviewRepository: mockReviewRepository, + accessTokenManager: mockAccesTokenManager, + userRepository: mockUserRepository, + spotifyRepository: mockSpotifyRepository, + //followRepository: mockFollowRepository, + likeOeuvreRepository : mocklikeOeuvreRepository, + oeuvreFavRepository : mockoeuvreFavRepository + } + describe("valid cases", ()=>{ + it("should return valid oeuvre with 1 artist", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((idUtilisateur) => mockUser) + // mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + // mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + // mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + return Promise.resolve(mockAlbumRaw); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre)); + mockSpotifyRepository.getSpotifyArtist = jest.fn((idArtist) => mockArtist); + + mocklikeOeuvreRepository.doesUserLikes = jest.fn((idUtilisateur,idOeuvre)).mockReturnValue(false) + mockoeuvreFavRepository.oeuvreFavExists = jest.fn((idUtilisateur,idOeuvre)).mockReturnValue(false) + + mocklikeOeuvreRepository.getLikeCount = jest.fn((idOeuvre) => 9) + mockReviewRepository.getReviewCount = jest.fn((idOeuvre) => 2) + mockReviewRepository.getOeuvreRating = jest.fn(() => 1) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(false) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) + + //mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + // mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + // mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + //mockReviewRepository.getReviewCount = jest.fn((id) => 2) + //mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + // mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + // mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + // mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) + const result = await getOeuvre(1,'token', serviceLocator) + result.albums[0].popularity = 64 + expect(result).toEqual(expectedOeuvre) + }) + }) + describe("invalid cases", ()=>{ + it("should throw error bad auth token", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => null) + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(401) + }) + it("should throw error artist not found", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => { + return { + error: { + status: 400, + message: "message", + } + } + }) + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + it("should throw error oeuvre not found 1", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ + error: { + status: 400, + message: "message", + } + }) + + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + it("should throw error oeuvre not found 2", async ()=>{ + mockAccesTokenManager.decode = jest.fn((userToken) => 1) + mockUserRepository.getByUser = jest.fn((id) => mockUser) + mockFollowRepository.getFollowersCount = jest.fn((artistId) => 100) + mockFollowRepository.getFriendsFollowing = jest.fn((artistId,userId,limit) => [mockUserPrivate]) + mockFollowRepository.getFriendsFollowingCount = jest.fn((artistId,userId) => 1) + mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) + mockSpotifyRepository.getSpotifyArtistSongs = jest.fn((artistId,filter,limit) => mockAlbumRaw) + mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) + mockReviewRepository.getReviewCount = jest.fn((id) => 2) + mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) + mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) + mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ + error: { + status: 400, + message: "message", + } + }) + + const error = await catchError(async ()=>{ + await getArtist(1,'token', serviceLocator) + }) + expect(error.code).toBe(400) + }) + + }) + +}) \ No newline at end of file From e9dcaf14b301699b822dc70b8bfeda7ce52a7ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Sun, 14 Apr 2024 22:56:15 +0200 Subject: [PATCH 52/67] Feature/comment rebase (#49) * started comment review * save * corrected insert test data * added put comment endpoint and corrected broken tests * started comment routes * added new function in comment repository * started comment review * save * corrected insert test data * added put comment endpoint and corrected broken tests * started comment routes * added new function in comment repository * finished comment endpoints without testing * added missing file * test * test * test correction * Merge branch 'main' into feature/comments * added created at to comments * corrected put comment * like comment * correctec count comment * removed limitation on private comment * added reviews/artist/{} * added missing file * corrected artist * added user in comments * added canDelete * correction des erreurs detecte au niveau front * merge * rebase correction --------- Co-authored-by: yousrah24 --- .../use_cases/comment/deleteComment.js | 14 + .../use_cases/comment/getComment.js | 151 +++++++++++ .../use_cases/comment/likeComment.js | 11 + .../use_cases/comment/putComment.js | 16 ++ .../use_cases/oeuvre/likeOeuvre.js | 2 +- .../use_cases/review/getArtistReviews.js | 31 +++ lib/application/use_cases/review/getReview.js | 16 +- .../use_cases/review/likeReview.js | 2 +- .../use_cases/review/putComment.js | 30 +++ .../use_cases/review/util/getReview.js | 2 +- lib/application/use_cases/user/follow.js | 2 +- lib/domain/entity/CommentEntity.js | 29 ++ lib/domain/entity/ReviewEntity.js | 33 ++- lib/domain/model/Comment.js | 12 + lib/domain/model/PublicComment.js | 11 + lib/domain/model/Review.js | 2 +- lib/domain/model/ReviewPublic.js | 5 +- lib/infrastructure/config/service-locator.js | 5 +- .../orm/sequelize/models/Commentaire.js | 4 + .../orm/sequelize/models/Review.js | 4 + .../repositories/CommentRepository.js | 253 ++++++++++++++++++ .../repositories/FriendRepository.js | 8 +- .../repositories/ReviewRepository.js | 34 +-- .../interfaces/CommentRepositoryAbstract.js | 44 +++ lib/infrastructure/webserver/server.js | 1 + .../controllers/CommentController.js | 61 +++++ .../controllers/ReviewController.js | 48 +++- lib/interfaces/routes/comment.js | 126 +++++++++ lib/interfaces/routes/review.js | 64 ++++- .../serializers/ReviewSerializer.js | 5 +- scripts/insert_test_data.py | 16 +- .../fixture/artist/getArtistFixture.js | 4 +- .../fixture/review/getOeuvreReviewsFixture.js | 2 +- .../fixture/review/getReviewFixture.js | 11 +- test/integration/oeuvre.test.js | 2 +- test/integration/review.test.js | 20 +- .../usecase/artist/getArtistFixture.js | 8 +- .../usecase/oeuvre/likeOeuvre.test.js | 4 +- .../review/fixture/getOeuvreReviewsFixture.js | 4 +- .../review/fixture/getReviewFixture.js | 22 +- .../review/fixture/getReviewsFixture.js | 76 ++++++ .../review/fixture/getUserReviewFixture.js | 95 +++++++ .../review/fixture/putReviewFixture.js | 4 +- .../usecase/review/getReview.test.js | 16 +- .../usecase/review/getReviews.test.js | 2 +- .../usecase/review/getUserReviews.test.js | 2 +- .../usecase/review/likeReview.test.js | 4 +- 47 files changed, 1231 insertions(+), 87 deletions(-) create mode 100644 lib/application/use_cases/comment/deleteComment.js create mode 100644 lib/application/use_cases/comment/getComment.js create mode 100644 lib/application/use_cases/comment/likeComment.js create mode 100644 lib/application/use_cases/comment/putComment.js create mode 100644 lib/application/use_cases/review/getArtistReviews.js create mode 100644 lib/application/use_cases/review/putComment.js create mode 100644 lib/domain/entity/CommentEntity.js create mode 100644 lib/domain/model/Comment.js create mode 100644 lib/domain/model/PublicComment.js create mode 100644 lib/infrastructure/repositories/CommentRepository.js create mode 100644 lib/infrastructure/repositories/interfaces/CommentRepositoryAbstract.js create mode 100644 lib/interfaces/controllers/CommentController.js create mode 100644 lib/interfaces/routes/comment.js create mode 100644 test/unit/application/usecase/review/fixture/getReviewsFixture.js create mode 100644 test/unit/application/usecase/review/fixture/getUserReviewFixture.js diff --git a/lib/application/use_cases/comment/deleteComment.js b/lib/application/use_cases/comment/deleteComment.js new file mode 100644 index 0000000..5696644 --- /dev/null +++ b/lib/application/use_cases/comment/deleteComment.js @@ -0,0 +1,14 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async ( + idComment, + userToken, + { accessTokenManager, userRepository, commentRepository } +) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value; + if (!(await userRepository.getByUser(id_utilisateur))) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + if (!(await commentRepository.canDelete(idComment, id_utilisateur))) + throwStatusCode(403, "ce n'est pas votre post"); + + await commentRepository.delete(idComment, id_utilisateur); +}; diff --git a/lib/application/use_cases/comment/getComment.js b/lib/application/use_cases/comment/getComment.js new file mode 100644 index 0000000..91c95e9 --- /dev/null +++ b/lib/application/use_cases/comment/getComment.js @@ -0,0 +1,151 @@ +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +const ReviewPublic = require("../../../domain/model/ReviewPublic"); +const PublicComment = require("../../../domain/model/PublicComment"); +const UserPublic = require("../../../domain/model/UserPublic"); + +module.exports = async ( + id_com, + page, + pageSize, + orderByLike, + userToken, + { + reviewRepository, + commentRepository, + accessTokenManager, + userRepository, + friendRepository, + spotifyRepository, + } +) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value; + if (!(await userRepository.getByUser(id_utilisateur))) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + const mainComment = await commentRepository.getById(id_com); + if (!mainComment) throwStatusCode(404, "commentaire introuvable"); + const commentsRaw = await commentRepository.getCommentsComments( + id_com, + id_utilisateur, + true, + page, + pageSize, + orderByLike + ); + const review = await reviewRepository.getById(mainComment.id_review); + let previousComments = []; + let nextCommentsId = mainComment.id_reponse; + while (nextCommentsId) { + previousComments.push(await commentRepository.getById(nextCommentsId)); + nextCommentsId = previousComments.at(-1).id_reponse; + } + return await serialize( + id_utilisateur, + mainComment, + previousComments, + commentsRaw, + review, + { spotifyRepository, friendRepository, commentRepository, reviewRepository } + ); +}; + +const serialize = async ( + id_utilisateur, + comment, + previousComments, + comments, + review, + { spotifyRepository, friendRepository, commentRepository, reviewRepository } +) => { + const serializeReview = async () => { + const rawOeuvre = await spotifyRepository.getOeuvre( + review.id_oeuvre, + review.type + ); + if (rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); + const doesUserLike = await reviewRepository.doesUserLike( + id_utilisateur, + review.id_review + ); + + return !review.utilisateur.is_private || + friendRepository.areFriends( + id_utilisateur, + review.utilisateur.id_utilisateur + ) + ? reviewSerializer( + review, + rawOeuvre, + review.utilisateur, + doesUserLike, + comments + ) + : { + private: true, + }; + }; + const serializeComment = async () => { + const doesUserLike = await commentRepository.doesUserLike( + id_utilisateur, + comment.id_com + ); + comment.doesUserLike = doesUserLike; + return !comment.utilisateur.is_private || + friendRepository.areFriends( + id_utilisateur, + comment.utilisateur.id_utilisateur + ) + ? new PublicComment(comment, new UserPublic(comment.utilisateur)) + : { + private: true, + }; + }; + const serializedPreviousComments = Promise.all( + previousComments.map(async (comment) => { + const doesUserLike = await commentRepository.doesUserLike( + id_utilisateur, + comment.id_com + ); + comment.doesUserLike = doesUserLike; + return !comment.utilisateur.is_private || + friendRepository.areFriends( + id_utilisateur, + comment.utilisateur.id_utilisateur + ) + ? new PublicComment(comment) + : { + private: true, + }; + }) + ); + const serializedComments = Promise.all( + comments.map(async (comment) => { + const doesUserLike = await commentRepository.doesUserLike( + id_utilisateur, + comment.id_com + ); + comment.doesUserLike = doesUserLike; + return !comment.utilisateur.is_private || + friendRepository.areFriends( + id_utilisateur, + comment.utilisateur.id_utilisateur + ) + ? new PublicComment(comment, new UserPublic(comment.utilisateur)) + : { + private: true, + }; + }) + ); + const promise = await Promise.all([ + serializeReview(), + serializeComment(), + serializedPreviousComments, + serializedComments, + ]); + return { + review: promise[0], + comment: promise[1], + previousComments: promise[2], + comments: promise[3], + }; +}; diff --git a/lib/application/use_cases/comment/likeComment.js b/lib/application/use_cases/comment/likeComment.js new file mode 100644 index 0000000..188bc7f --- /dev/null +++ b/lib/application/use_cases/comment/likeComment.js @@ -0,0 +1,11 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +module.exports = async (commentId,userToken,{accessTokenManager,userRepository,commentRepository}) =>{ + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await commentRepository.getById(commentId)) throwStatusCode(404,"commentaire introuvable") + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + if(! await commentRepository.doesUserLike(commentId,id_utilisateur)){ + await commentRepository.like(commentId,id_utilisateur,) + return true + } + await commentRepository.unlike(commentId,id_utilisateur) +} \ No newline at end of file diff --git a/lib/application/use_cases/comment/putComment.js b/lib/application/use_cases/comment/putComment.js new file mode 100644 index 0000000..838d54b --- /dev/null +++ b/lib/application/use_cases/comment/putComment.js @@ -0,0 +1,16 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const PublicComment = require("../../../domain/model/PublicComment") +const UserPublic = require("../../../domain/model/UserPublic") +module.exports = async ( + commentId, + description, + userToken, + {friendRepository,accessTokenManager,commentRepository,userRepository}) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + const comment = await commentRepository.getById(commentId) + if(comment.utilisateur.is_private && !(await friendRepository.areFriends(id_utilisateur,comment.utilisateur.id_utilisateur))) + throwStatusCode(403, "l'utilisateur est en privé") + const commentRaw = await commentRepository.persist(comment.id_review,description,id_utilisateur,commentId) + return new PublicComment(commentRaw,new UserPublic(comment.utilisateur)) +} \ No newline at end of file diff --git a/lib/application/use_cases/oeuvre/likeOeuvre.js b/lib/application/use_cases/oeuvre/likeOeuvre.js index 2e12fed..37e1d05 100644 --- a/lib/application/use_cases/oeuvre/likeOeuvre.js +++ b/lib/application/use_cases/oeuvre/likeOeuvre.js @@ -4,7 +4,7 @@ module.exports = async (userToken, artistId,type, {userRepository,likeOeuvreRepo if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") const oeuvre = spotifyRepository.getOeuvre(artistId,type) if(oeuvre.error) throwStatusCode(oeuvre.error.status,oeuvre.error.message) - if(! await likeOeuvreRepository.doesUserLikes(id_utilisateur,artistId)) { + if(! await likeOeuvreRepository.doesUserLike(id_utilisateur,artistId)) { await likeOeuvreRepository.like(id_utilisateur,artistId) return true } diff --git a/lib/application/use_cases/review/getArtistReviews.js b/lib/application/use_cases/review/getArtistReviews.js new file mode 100644 index 0000000..c67377e --- /dev/null +++ b/lib/application/use_cases/review/getArtistReviews.js @@ -0,0 +1,31 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async (artistId, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager}) => { + let id = null + if (userToken) { + id = accessTokenManager.decode(userToken)?.value + } + const artist = await spotifyRepository.getSpotifyArtist(artistId) + if(artist.error) + throwStatusCode(artist.error.status,artist.error.message) + const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single') + const album_ids = albums.items.map((album)=>album.id) + const reviews = await reviewRepository.getOeuvreReviews(page,pageSize, orderByLike,false,album_ids,id) + const serializedReviews = [] + reviews.forEach(element => { + + serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) + }) + return await Promise.all(serializedReviews) +} + + +const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { + let doesUserLike = false + if(id_utilisateur) { + doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) + } + const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) + if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) +} \ No newline at end of file diff --git a/lib/application/use_cases/review/getReview.js b/lib/application/use_cases/review/getReview.js index c926377..4ac5b80 100644 --- a/lib/application/use_cases/review/getReview.js +++ b/lib/application/use_cases/review/getReview.js @@ -1,17 +1,21 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); const getReview = require("./util/getReview") -module.exports = async (idReview,userToken, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,followRepository}) => { - - const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository,followRepository}) +module.exports = async (idReview,userToken,page,pageSize,orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,commentRepository}) => { + + const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository}) const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + + let doesUserLike = false + let id_utilisateur = null if(userToken) { - const id_utilisateur = accessTokenManager.decode(userToken)?.value + id_utilisateur = accessTokenManager.decode(userToken)?.value doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) + } + const comments = await commentRepository.getReviewComments(rawReview.id_review,id_utilisateur,false,page,pageSize,orderByLike) + return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike,comments) } \ No newline at end of file diff --git a/lib/application/use_cases/review/likeReview.js b/lib/application/use_cases/review/likeReview.js index 5aed82e..e1c501e 100644 --- a/lib/application/use_cases/review/likeReview.js +++ b/lib/application/use_cases/review/likeReview.js @@ -2,7 +2,7 @@ const throwStatusCode = require("../utils/throwStatusCode"); module.exports = async (reviewId,userToken,{accessTokenManager,userRepository,reviewRepository}) =>{ const id_utilisateur = accessTokenManager.decode(userToken)?.value if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - if(! await reviewRepository.doesUserLikes(id_utilisateur,reviewId)){ + if(! await reviewRepository.doesUserLike(id_utilisateur,reviewId)){ await reviewRepository.likeReview(id_utilisateur,reviewId) return true } diff --git a/lib/application/use_cases/review/putComment.js b/lib/application/use_cases/review/putComment.js new file mode 100644 index 0000000..48abdf0 --- /dev/null +++ b/lib/application/use_cases/review/putComment.js @@ -0,0 +1,30 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +const getReview = require("./util/getReview"); +module.exports = async ( + userToken, + idReview, + description, + { + userRepository, + accessTokenManager, + friendRepository, + reviewRepository, + commentRepository, + } +) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value; + if (!(await userRepository.getByUser(id_utilisateur))) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + await getReview(idReview, userToken, { + accessTokenManager, + friendRepository, + reviewRepository, + }); + return await commentRepository.persist( + idReview, + description, + id_utilisateur, + null + ); +}; diff --git a/lib/application/use_cases/review/util/getReview.js b/lib/application/use_cases/review/util/getReview.js index 8ae971d..896b171 100644 --- a/lib/application/use_cases/review/util/getReview.js +++ b/lib/application/use_cases/review/util/getReview.js @@ -1,6 +1,6 @@ const throwStatusCode = require("../../utils/throwStatusCode"); -module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository,followRepository}) => { +module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository}) => { const rawReview = await reviewRepository.getById(idReview) if(!rawReview) throwStatusCode(404,"la review n'existe pas") diff --git a/lib/application/use_cases/user/follow.js b/lib/application/use_cases/user/follow.js index ce518c6..823ac37 100644 --- a/lib/application/use_cases/user/follow.js +++ b/lib/application/use_cases/user/follow.js @@ -2,7 +2,7 @@ const throwStatusCode = require("../utils/throwStatusCode"); module.exports = async (userToken, artistId, {userRepository,followRepository,accessTokenManager,spotifyRepository}) =>{ const id = accessTokenManager.decode(userToken)?.value if(! await userRepository.getByUser(id)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - const artist = spotifyRepository.getSpotifyArtist(artistId) + const artist = await spotifyRepository.getSpotifyArtist(artistId) if(artist.error) throwStatusCode(artist.error.status,artist.error.message) if(! await followRepository.doesFollows(id,artistId)) { followRepository.follow(id,artistId) diff --git a/lib/domain/entity/CommentEntity.js b/lib/domain/entity/CommentEntity.js new file mode 100644 index 0000000..9504280 --- /dev/null +++ b/lib/domain/entity/CommentEntity.js @@ -0,0 +1,29 @@ +const Joi = require('joi') +const getCommentParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) +const deleteCommentParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) +const likeCommentParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) +const putCommentParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) +const putCommentPayload = Joi.object().keys({ + description: Joi.string().max(1500).required() +}) +const getCommentQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean() +}) +module.exports = { + getCommentParams, + deleteCommentParams, + likeCommentParams, + putCommentPayload, + putCommentParams, + getCommentQuery +} \ No newline at end of file diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index 6836c48..9d33b03 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -39,11 +39,15 @@ const getReviewParams = Joi.object().keys({ .max(50) .required() }) - +const getReviewQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean() +}) const getReviews = Joi.object().keys({ page: Joi.number().integer().min(1).required(), pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean().invalid(false) + orderByLike: Joi.boolean() }) const likeReviewParams = Joi.object().keys({ @@ -74,6 +78,24 @@ const oeuvreReviewQuery = Joi.object().keys({ pageSize: Joi.number().integer().min(1).required(), orderByLike: Joi.boolean() }) + + +const artistReviewParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) + +const artistReviewQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean() +}) +const putCommentParams = Joi.object().keys({ + id: Joi.string().max(50).required() +}) + +const putCommentPayload = Joi.object().keys({ + description: Joi.string().max(1500).required() +}) module.exports = { putReview, deleteReview, @@ -83,6 +105,11 @@ module.exports = { likeReviewQuery, userReviewParams, userReviewQuery, + putCommentParams, + oeuvreReviewQuery, + putCommentPayload, + getReviewQuery, oeuvreReviewParams, - oeuvreReviewQuery + artistReviewParams, + artistReviewQuery, } \ No newline at end of file diff --git a/lib/domain/model/Comment.js b/lib/domain/model/Comment.js new file mode 100644 index 0000000..74f2f95 --- /dev/null +++ b/lib/domain/model/Comment.js @@ -0,0 +1,12 @@ +module.exports = class { + constructor(comment,utilisateur) { + this.id_com = comment.id_com + this.id_review = comment.id_review + this.id_reponse = comment.id_reponse + this.description = comment.description + this.countLike = comment.countLike + this.countComment = comment.countComment + this.createdAt = comment.createdAt + this.utilisateur = utilisateur + } +} \ No newline at end of file diff --git a/lib/domain/model/PublicComment.js b/lib/domain/model/PublicComment.js new file mode 100644 index 0000000..d1eb4bc --- /dev/null +++ b/lib/domain/model/PublicComment.js @@ -0,0 +1,11 @@ +module.exports = class { + constructor(comment,utilisateur) { + this.id_com = comment.id_com + this.countLike = comment.countLike + this.countComment = comment.countComment + this.description = comment.description + this.doesUserLike = !!comment.doesUserLike + this.createdAt = comment.createdAt + this.utilisateur = utilisateur + } +} \ No newline at end of file diff --git a/lib/domain/model/Review.js b/lib/domain/model/Review.js index df207c4..a856510 100644 --- a/lib/domain/model/Review.js +++ b/lib/domain/model/Review.js @@ -2,7 +2,7 @@ module.exports = class { constructor(rawReview, utilisateur,type) { this.id_review = rawReview.id_review this.id_oeuvre = rawReview.id_oeuvre - this.countlikes = rawReview.dataValues.countLike + this.countlike = rawReview.dataValues.countLike this.countComment = rawReview.dataValues.countComment this.description = rawReview.description this.note = rawReview.note diff --git a/lib/domain/model/ReviewPublic.js b/lib/domain/model/ReviewPublic.js index 2d5f7bc..057676a 100644 --- a/lib/domain/model/ReviewPublic.js +++ b/lib/domain/model/ReviewPublic.js @@ -1,14 +1,15 @@ module.exports = class { - constructor(rawReview,oeuvre,utilisateur,doesUserLike) { + constructor(rawReview,oeuvre,utilisateur,doesUserLike,comments) { this.id_review = rawReview.id_review this.description = rawReview.description - this.countlikes = rawReview.countlikes + this.countlike = rawReview.countlike this.countComment = rawReview.countComment this.doesUserLike = doesUserLike this.note = rawReview.note this.createdAt = rawReview.createdAt this.oeuvre = oeuvre this.utilisateur = utilisateur + this.comments = comments this.type = rawReview.type } } \ No newline at end of file diff --git a/lib/infrastructure/config/service-locator.js b/lib/infrastructure/config/service-locator.js index 59b6139..ff71ea1 100644 --- a/lib/infrastructure/config/service-locator.js +++ b/lib/infrastructure/config/service-locator.js @@ -10,9 +10,9 @@ const FollowRepository= require('../repositories/FollowRepository'); const FriendRepository= require('../repositories/FriendRepository'); const ReviewRepository= require('../repositories/ReviewRepository'); const LikeOeuvreRepository= require('../repositories/LikeOeuvreRepository'); +const CommentRepository= require('../repositories/CommentRepository'); const OeuvreFavRepository= require('../repositories/OeuvreFavRepository'); - function buildBeans() { return { accessTokenManager: new JwtAccessTokenManager(), @@ -24,7 +24,8 @@ function buildBeans() { friendRepository: new FriendRepository(), oeuvreFavRepository: new OeuvreFavRepository(), reviewRepository: new ReviewRepository(), - likeOeuvreRepository: new LikeOeuvreRepository() + likeOeuvreRepository: new LikeOeuvreRepository(), + commentRepository: new CommentRepository() }; } diff --git a/lib/infrastructure/orm/sequelize/models/Commentaire.js b/lib/infrastructure/orm/sequelize/models/Commentaire.js index 7c5b24e..2b74d08 100644 --- a/lib/infrastructure/orm/sequelize/models/Commentaire.js +++ b/lib/infrastructure/orm/sequelize/models/Commentaire.js @@ -13,6 +13,10 @@ module.exports = (sequelize) => { type: DataTypes.STRING(1500), allowNull: false, }, + deleted: { + type: DataTypes.BOOLEAN, + defaultValue: false + } }, { freezeTableName: true,}); diff --git a/lib/infrastructure/orm/sequelize/models/Review.js b/lib/infrastructure/orm/sequelize/models/Review.js index 8d9a18d..b101ca3 100644 --- a/lib/infrastructure/orm/sequelize/models/Review.js +++ b/lib/infrastructure/orm/sequelize/models/Review.js @@ -19,6 +19,10 @@ module.exports = (sequelize) => { note: { type: DataTypes.INTEGER, }, + deleted: { + type: DataTypes.BOOLEAN, + defaultValue: false + } }, { freezeTableName: true,}); diff --git a/lib/infrastructure/repositories/CommentRepository.js b/lib/infrastructure/repositories/CommentRepository.js new file mode 100644 index 0000000..18a7a95 --- /dev/null +++ b/lib/infrastructure/repositories/CommentRepository.js @@ -0,0 +1,253 @@ +"use strict"; +const { Op } = require("sequelize"); +const sequelize = require("../orm/sequelize/sequelize"); +const CommentRepository = require("./interfaces/CommentRepositoryAbstract"); +const Comment = require("../../domain/model/Comment"); +const Utilisateur = require("../../domain/model/UserPublic"); +module.exports = class extends CommentRepository { + constructor() { + super(); + this.db = sequelize; + this.comment = this.db.model("commentaire"); + this.user = this.db.model("utilisateur"); + this.likeCommentaire = this.db.model("like_commentaire"); + } + + createComment(commentRaw) { + return new Comment(commentRaw, new Utilisateur(commentRaw.user_review)); + } + async persist(id_review, description, id_utilisateur, id_reponse = null) { + const seqComment = await this.comment.create({ + id_review, + description, + id_utilisateur, + id_reponse, + }); + return await this.getById(seqComment.id_com); + } + + async getCommentsComments( + id_reponse, + id_utilisateur, + fetchPrivate, + page, + pageSize, + orderByLike + ) { + let whereClause = { + id_reponse, + deleted: false, + }; + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + if (!fetchPrivate) { + whereClause.id_utilisateur = id_utilisateur + ? { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))` + ), + } + : { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false` + ), + }; + } + const comments = await this.comment.findAll({ + offset: offset, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "user_review", + }, + // { + // model: this.review, + // include: [ + // { + // model: this.user, + // as: "utilisateur", + // } + // ], + // } + ], + where: whereClause, + order, + limit: pageSize, + }); + return comments.map((comment) => this.createComment(comment.dataValues)); + } + async delete(id_com, id_utilisateur) { + const seqComment = await this.comment.findOne({ + where: { + id_com, + id_utilisateur, + }, + }); + if (!seqComment) return false; + seqComment.deleted = true; + await seqComment.save(); + return true; + } + + async getById(id_com, deleted = false) { + const comment = await this.comment.findOne({ + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "user_review", + }, + // { + // model: this.review, + // include: [ + // { + // model: this.user, + // as: "utilisateur", + // } + // ], + // } + ], + where: { + id_com, + deleted, + }, + }); + return comment ? this.createComment(comment.dataValues) : null; + } + + async doesUserLike(id_com, id_utilisateur) { + const count = await this.likeCommentaire.count({ + where: { + id_utilisateur, + id_com, + }, + }); + return count > 0; + } + async like(id_com, id_utilisateur) { + const seqLike = await this.likeCommentaire.create({ + id_com, + id_utilisateur, + }); + await seqLike.save(); + } + unlike(id_com, id_utilisateur) { + this.likeCommentaire.destroy({ + where: { + id_utilisateur, + id_com, + }, + }); + } + + async getReviewComments( + id_review, + id_utilisateur, + fetchPrivate, + page, + pageSize, + orderByLike + ) { + let whereClause = { + id_review, + id_reponse: null, + deleted: false, + }; + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + if (!fetchPrivate) { + whereClause.id_utilisateur = id_utilisateur + ? { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))` + ), + } + : { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false` + ), + }; + } + + const comments = await this.comment.findAll({ + offset: offset, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "user_review", + }, + // { + // model: this.review, + // include: [ + // { + // model: this.user, + // as: "utilisateur", + // } + // ], + // } + ], + where: whereClause, + order, + limit: pageSize, + }); + return comments.map((comment) => this.createComment(comment.dataValues)); + } + async canDelete(id_com, id_utilisateur) { + const count = await this.comment.count({ + where: { + id_com, + id_utilisateur, + }, + }); + return count !== 0; + } +}; diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index 037789e..679de76 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -101,14 +101,16 @@ module.exports = class extends FriendRepositoryAbstract { where: { [Op.or]: [ { id_utilisateur: id, - amiIdUtilisateur: amiIdUtilisateur + amiIdUtilisateur: amiIdUtilisateur, + en_attente: false }, { id_utilisateur: amiIdUtilisateur, - amiIdUtilisateur: id + amiIdUtilisateur: id, + en_attente: false }, ] }, }); - return count === 2 + return count > 0 } }; diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js index 24237ed..2f2f9bc 100644 --- a/lib/infrastructure/repositories/ReviewRepository.js +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -34,7 +34,8 @@ module.exports = class extends ReviewRepository { return await this.review.findOne({ where: { id_oeuvre, - id_utilisateur + id_utilisateur, + deleted: false } }) } @@ -56,18 +57,15 @@ module.exports = class extends ReviewRepository { } }); if(!seqReview) return false - await this.review.destroy({ - where: { - id_review, - id_utilisateur - } - }); + seqReview.deleted = true + seqReview.save() return true } - async getById(id_review){ + async getById(id_review,deleted = false){ const seqReview = await this.review.findOne({ where: { - id_review: id_review + id_review: id_review, + deleted }, attributes: { include: [ @@ -101,7 +99,9 @@ module.exports = class extends ReviewRepository { const orderColumn = orderByLike ? "countLike" : "createdAt"; const order = [[sequelize.literal(orderColumn), "DESC"]]; const offset = (page - 1) * pageSize; - let whereClause = {} + let whereClause = { + deleted: false + } if(!fetchPrivate) { whereClause.id_utilisateur = id ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} @@ -142,15 +142,14 @@ module.exports = class extends ReviewRepository { async getOeuvreReviews(page,pageSize,orderByLike, fetchPrivate,ids_oeuvre, id) { - console.log("TEST") const orderColumn = orderByLike ? "countLike" : "createdAt"; const order = [[sequelize.literal(orderColumn), "DESC"]]; const offset = (page - 1) * pageSize; let whereClause if (Array.isArray(ids_oeuvre) && ids_oeuvre.length > 0) { - whereClause = { id_oeuvre: { [Op.in]: ids_oeuvre } }; + whereClause = { id_oeuvre: { [Op.in]: ids_oeuvre }, deleted: false }; } else { - whereClause = { id_oeuvre: ids_oeuvre}; + whereClause = { id_oeuvre: ids_oeuvre, deleted: false}; } if(!fetchPrivate) { whereClause.id_utilisateur = id @@ -266,7 +265,8 @@ module.exports = class extends ReviewRepository { } ], where: { - id_utilisateur + id_utilisateur, + deleted: false }, order }) @@ -276,7 +276,8 @@ module.exports = class extends ReviewRepository { async getOeuvreRating(id_oeuvre) { const seqReview = await this.review.findOne({ where: { - id_oeuvre + id_oeuvre, + deleted: false }, attributes: [ [sequelize.fn('AVG', sequelize.col('note')), 'rating'] @@ -298,7 +299,8 @@ module.exports = class extends ReviewRepository { async getReviewCount(id_oeuvre) { return await this.review.count({ where: { - id_oeuvre + id_oeuvre, + deleted: false }, }) } diff --git a/lib/infrastructure/repositories/interfaces/CommentRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/CommentRepositoryAbstract.js new file mode 100644 index 0000000..45bf5c3 --- /dev/null +++ b/lib/infrastructure/repositories/interfaces/CommentRepositoryAbstract.js @@ -0,0 +1,44 @@ +"use strict"; + +module.exports = class { + persist(id_review, description, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + + getReviewComments( + id_review, + id_utilisateur, + fetchPrivate, + page, + pageSize, + orderByLike + ) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + getCommentsComments( + id_reponse, + id_utilisateur, + fetchPrivate, + page, + pageSize, + orderByLike + ) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + delete(id_comment, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + doesUserLikes(id_comment, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + like(id_comment, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + unlike(id_comment, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } + + canDelete(id_comment, id_utilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); + } +}; diff --git a/lib/infrastructure/webserver/server.js b/lib/infrastructure/webserver/server.js index 1fb03ab..0a7a19b 100644 --- a/lib/infrastructure/webserver/server.js +++ b/lib/infrastructure/webserver/server.js @@ -66,6 +66,7 @@ const createServer = async () => { require('../../interfaces/routes/friends'), require('../../interfaces/routes/artist'), require('../../interfaces/routes/oeuvre'), + require('../../interfaces/routes/comment') ]); refreshTokens(serviceLocator) removeExpiredConfirmTokens(serviceLocator) diff --git a/lib/interfaces/controllers/CommentController.js b/lib/interfaces/controllers/CommentController.js new file mode 100644 index 0000000..6c07a82 --- /dev/null +++ b/lib/interfaces/controllers/CommentController.js @@ -0,0 +1,61 @@ +const getComment = require("../../application/use_cases/comment/getComment"); +const deleteComment = require("../../application/use_cases/comment/deleteComment"); +const putComment = require("../../application/use_cases/comment/putComment"); +const likeComment = require("../../application/use_cases/comment/likeComment"); +const handleError = require("./utils/handleError"); +module.exports = { + + async get(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {id} = request.params + const {page, pageSize, orderByLike} = request.query + return handler.response(await getComment(id,page, pageSize, orderByLike, token, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async delete(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {id} = request.params + + await deleteComment(id, token, serviceLocator) + return handler.response('').code(200) + }catch(error){ + return handleError(error) + } + }, + async put(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {id} = request.params + const {description} = request.payload + return handler.response(await putComment(id,description,token, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async like(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token ] = authorizationHeader.split(' '); + const {id} = request.params + await likeComment(id,token, serviceLocator) + return handler.response('').code(200) + }catch(error){ + return handleError(error) + } + }, +} \ No newline at end of file diff --git a/lib/interfaces/controllers/ReviewController.js b/lib/interfaces/controllers/ReviewController.js index 44a7abd..991021b 100644 --- a/lib/interfaces/controllers/ReviewController.js +++ b/lib/interfaces/controllers/ReviewController.js @@ -6,6 +6,8 @@ const likeReview = require("../../application/use_cases/review/likeReview"); const getReviewLikes = require("../../application/use_cases/review/getReviewLikes"); const getUserReviews = require("../../application/use_cases/review/getUserReviews"); const getOeuvreReviews = require("../../application/use_cases/review/getOeuvreReviews"); +const getArtistReviews = require("../../application/use_cases/review/getArtistReviews"); +const putComment = require("../../application/use_cases/review/putComment"); const getCountOeuvreReviews = require("../../application/use_cases/review/getCountOeuvreReviews"); const handleError = require("./utils/handleError"); @@ -42,9 +44,10 @@ module.exports = { // Context const serviceLocator = request.server.app.serviceLocator; // a tous les repo const {id} = request.params + const {page, pageSize,orderByLike} = request.query const authorizationHeader = request.headers.authorization; const token = authorizationHeader?.split(' ')[1]; - return handler.response(await getReview(id,token, serviceLocator)).code(200) + return handler.response(await getReview(id,token,page, pageSize,orderByLike, serviceLocator)).code(200) }catch(error){ return handleError(error) } @@ -117,4 +120,47 @@ module.exports = { return handleError(error) } }, + async getArtistReviews(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {page, pageSize,orderByLike} = request.query + return handler.response(await getArtistReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + + async putComment(request,handler){ + try{ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {description} = request.payload + return handler.response(await putComment(token, id,description, serviceLocator)).code(200) + }catch(error){ + return handleError(error) + } + }, + async getOeuvreReviews(request,handler){ + try{ + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(' ')[1]; + const {id} = request.params + const {page, pageSize,orderByLike} = request.query + const response = { + data: await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator), + count: await getCountOeuvreReviews(id,token, serviceLocator) + }; + return handler.response(response).code(200) + }catch(error){ + return handleError(error) + } + }, } \ No newline at end of file diff --git a/lib/interfaces/routes/comment.js b/lib/interfaces/routes/comment.js new file mode 100644 index 0000000..ba103ec --- /dev/null +++ b/lib/interfaces/routes/comment.js @@ -0,0 +1,126 @@ +const CommentController = require("../controllers/CommentController"); +const { + getCommentParams, + deleteCommentParams, + likeCommentParams, + putCommentPayload, + putCommentParams, + getCommentQuery +} = require("../../domain/entity/CommentEntity"); +module.exports = { + name: 'comment', + version: '1.0.0', + register: async (server) => { + server.route([ + { + method: 'GET', + path: '/comment/{id}', + handler: CommentController.get, + options: { + description: 'get a comment', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: getCommentParams, + query: getCommentQuery + } + }, + }, + { + method: 'DELETE', + path: '/comment/{id}', + handler: CommentController.delete, + options: { + auth: 'jwt', + description: 'delete a comment', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: deleteCommentParams + } + }, + }, + { + method: 'POST', + path: '/comment/{id}/like', + handler: CommentController.like, + options: { + auth: 'jwt', + description: 'like a comment', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: likeCommentParams, + } + } + }, + { + method: 'PUT', + path: '/comment/{id}', + handler: CommentController.put, + options: { + auth: 'jwt', + description: 'get review list', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + payload: putCommentPayload, + params: putCommentParams, + } + } + }, + ]) + } +}; \ No newline at end of file diff --git a/lib/interfaces/routes/review.js b/lib/interfaces/routes/review.js index 97a9301..8d71cbb 100644 --- a/lib/interfaces/routes/review.js +++ b/lib/interfaces/routes/review.js @@ -4,12 +4,17 @@ const { deleteReview, getReviews, getReviewParams, + getReviewQuery, likeReviewParams, likeReviewQuery, userReviewParams, + putCommentParams, + putCommentPayload, userReviewQuery, oeuvreReviewParams, - oeuvreReviewQuery + oeuvreReviewQuery, + artistReviewParams, + artistReviewQuery, } = require("../../domain/entity/ReviewEntity"); module.exports = { name: 'review', @@ -93,6 +98,7 @@ module.exports = { }, validate: { params: getReviewParams, + query: getReviewQuery } } }, @@ -229,8 +235,62 @@ module.exports = { query: oeuvreReviewQuery } } + }, + { + method: 'GET', + path: '/reviews/artist/{id}', + handler: ReviewController.getArtistReviews, + options: { + description: 'get artitst\' reviews', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: artistReviewParams, + query: artistReviewQuery + } + } + }, + { + method: 'PUT', + path: '/review/{id}/comment', + handler: ReviewController.putComment, + options: { + auth: 'jwt', + description: 'put a comment', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 204: {description: 'No content'}, + 401: {description: 'Unauthorized'}, + 403: {description: 'forbidden'}, + 404: {description: 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 502: {description: 'bad gateway'}, + 503: {description: 'Service unavailable'}, + } + } + }, + validate: { + params: putCommentParams, + payload: putCommentPayload + } + } } - //GET USER LIKES MAYBE ]); } }; \ No newline at end of file diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js index 3ebac43..8850a76 100644 --- a/lib/interfaces/serializers/ReviewSerializer.js +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -1,10 +1,9 @@ -const Album = require("../../domain/model/Album"); const artistSerializer = require("./ArtistSerializer"); const trackSerializer = require("./TrackSerializer"); const albumSerializer = require("./AlbumSerializer"); const UserPublic = require("../../domain/model/UserPublic") const ReviewPublic = require("../../domain/model/ReviewPublic"); -const serializeReview = (rawReview,rawOeuvre,utilisateur,doesUserLike) => { +const serializeReview = (rawReview,rawOeuvre,utilisateur,doesUserLike,comments) => { switch (rawReview.type){ case 'artist': rawOeuvre = artistSerializer(rawOeuvre) @@ -21,7 +20,7 @@ const serializeReview = (rawReview,rawOeuvre,utilisateur,doesUserLike) => { default: rawOeuvre = null } - return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur),doesUserLike) + return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur),doesUserLike,comments) } module.exports = serializeReview \ No newline at end of file diff --git a/scripts/insert_test_data.py b/scripts/insert_test_data.py index cfd9690..4c1f88f 100644 --- a/scripts/insert_test_data.py +++ b/scripts/insert_test_data.py @@ -57,11 +57,14 @@ def insert_follow(cursor,ids,artists): "nb_suivis": 0, "id_artiste": artist, } - insert_reponse = """ - INSERT INTO `artiste`(`nb_suivis`,`id_artiste`, `createdAt`, `updatedAt` ) - VALUES (%(nb_suivis)s,%(id_artiste)s,%(createdAt)s,%(updatedAt)s) - """ - cursor.execute(insert_reponse, data_to_insert) + try: + insert_reponse = """ + INSERT INTO `artiste`(`nb_suivis`,`id_artiste`, `createdAt`, `updatedAt` ) + VALUES (%(nb_suivis)s,%(id_artiste)s,%(createdAt)s,%(updatedAt)s) + """ + cursor.execute(insert_reponse, data_to_insert) + except Exception as err: + print(f"Artist already exist") for artist in artists: for id_utilisateur in ids: if(random.random() > 0.85): continue @@ -79,7 +82,6 @@ def insert_follow(cursor,ids,artists): def putReponse(cursor,reponse,ids, review_ids): reponse_ids = [] for i in range(len(review_ids)): - i = review_ids rid = None for y in range(random.randint(0,5)): date = faker.Faker().date_between(start_date='-1y', end_date='today') @@ -88,7 +90,7 @@ def putReponse(cursor,reponse,ids, review_ids): "createdAt": date, "updatedAt": date, "id_utilisateur": random.choice(ids), - "id_review": review_ids[y], + "id_review": review_ids[i], "id_reponse": rid, } insert_reponse = """ diff --git a/test/integration/fixture/artist/getArtistFixture.js b/test/integration/fixture/artist/getArtistFixture.js index 6cd7491..1f2ae66 100644 --- a/test/integration/fixture/artist/getArtistFixture.js +++ b/test/integration/fixture/artist/getArtistFixture.js @@ -78,7 +78,7 @@ const actualDate = new Date() const mockLikedReview = { "id_review": 25, "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlikes": 32, + "countlike": 32, "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, @@ -109,7 +109,7 @@ const mockLikedReview = { const mockCommentedReview = { "id_review": 25, "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlikes": 32, + "countlike": 32, "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, diff --git a/test/integration/fixture/review/getOeuvreReviewsFixture.js b/test/integration/fixture/review/getOeuvreReviewsFixture.js index eba8825..16508d7 100644 --- a/test/integration/fixture/review/getOeuvreReviewsFixture.js +++ b/test/integration/fixture/review/getOeuvreReviewsFixture.js @@ -28,7 +28,7 @@ const actualDate = new Date() const rawReview = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, diff --git a/test/integration/fixture/review/getReviewFixture.js b/test/integration/fixture/review/getReviewFixture.js index b465e4e..b933e54 100644 --- a/test/integration/fixture/review/getReviewFixture.js +++ b/test/integration/fixture/review/getReviewFixture.js @@ -28,7 +28,7 @@ const actualDate = new Date() const rawReview = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, @@ -38,11 +38,18 @@ const rawReview = { utilisateur: mockUser } - +const mockComments = [ + { + id: 298, + description: 'test4', + utilisateur: mockUser + } +] module.exports = { rawReview, mockArtist, + mockComments, } \ No newline at end of file diff --git a/test/integration/oeuvre.test.js b/test/integration/oeuvre.test.js index bdfb448..5db7eae 100644 --- a/test/integration/oeuvre.test.js +++ b/test/integration/oeuvre.test.js @@ -83,7 +83,7 @@ describe('artist route', () => { mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) - mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(false) + mockLikeOeuvreRepository.doesUserLike = jest.fn().mockReturnValue(false) mockLikeOeuvreRepository.like = jest.fn() const res1 = await server.inject({ method: 'POST', diff --git a/test/integration/review.test.js b/test/integration/review.test.js index 5fd7826..b2ec066 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -11,6 +11,7 @@ const mockFriendRepository = {} const mockAccesTokenManager = {} const mockSpotifyRepository = {} const mockUserRepository = {} +const mockCommentRepository = {} mockAccesTokenManager.generate = ((test) =>{return ''}) const mockToken = jwt.sign({ @@ -33,7 +34,8 @@ describe('review route', () => { friendRepository: mockFriendRepository, accessTokenManager: mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, - userRepository: mockUserRepository + userRepository: mockUserRepository, + commentRepository: mockCommentRepository } server.register(Jwt) server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); @@ -49,25 +51,28 @@ describe('review route', () => { const { mockArtist, rawReview, + mockComments } = require("./fixture/review/getReviewFixture") describe("GET review route", ()=>{ it("should return status code 200", async () => { - + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) mockReviewRepository.getById = jest.fn((id) => rawReview) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) const res1 = await server.inject({ method: 'GET', - url: `/review/1`, + url: `/review/1?page=1&pageSize=10&orderByLike=true`, }); expect(res1.statusCode).toBe(200); }) it("should return status code 404", async () => { mockReviewRepository.getById = jest.fn((id) => null) + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) const res1 = await server.inject({ method: 'GET', - url: `/review/1`, + url: `/review/1?page=1&pageSize=10&orderByLike=true`, }); expect(res1.statusCode).toBe(404); @@ -80,20 +85,23 @@ describe('review route', () => { is_private: true } } + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) mockReviewRepository.getById = jest.fn((id) => mockReview) mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) const res1 = await server.inject({ method: 'GET', - url: `/review/1`, + url: `/review/1?page=1&pageSize=10&orderByLike=true`, headers: { Authorization: `Bearer ${mockToken}` } + }); expect(res1.statusCode).toBe(403); }) it("should return status code 400", async () => { + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) mockReviewRepository.getById = jest.fn((id) => rawReview) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => { return { @@ -105,7 +113,7 @@ describe('review route', () => { }) const res1 = await server.inject({ method: 'GET', - url: `/review/1`, + url: `/review/1?page=1&pageSize=10&orderByLike=true`, headers: { Authorization: `Bearer ${mockToken}` } diff --git a/test/unit/application/usecase/artist/getArtistFixture.js b/test/unit/application/usecase/artist/getArtistFixture.js index bbda437..a41748b 100644 --- a/test/unit/application/usecase/artist/getArtistFixture.js +++ b/test/unit/application/usecase/artist/getArtistFixture.js @@ -78,7 +78,7 @@ const actualDate = new Date() const mockLikedReview = { "id_review": 25, "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlikes": 32, + "countlike": 32, "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, @@ -109,7 +109,7 @@ const mockLikedReview = { const mockCommentedReview = { "id_review": 25, "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlikes": 32, + "countlike": 32, "countComment": 0, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", "note": 5, @@ -260,7 +260,7 @@ const expectedArtist = { { "id_review": 25, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "countlikes": 32, + "countlike": 32, "countComment": 0, "doesUserLike": true, "createdAt": "2024-01-03T00:00:00.000Z", @@ -322,7 +322,7 @@ const expectedArtist = { { "id_review": 25, "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "countlikes": 32, + "countlike": 32, "createdAt": "2024-01-03T00:00:00.000Z", "countComment": 0, "doesUserLike": true, diff --git a/test/unit/application/usecase/oeuvre/likeOeuvre.test.js b/test/unit/application/usecase/oeuvre/likeOeuvre.test.js index 18b8421..70980ee 100644 --- a/test/unit/application/usecase/oeuvre/likeOeuvre.test.js +++ b/test/unit/application/usecase/oeuvre/likeOeuvre.test.js @@ -52,7 +52,7 @@ describe("like oeuvre use case ", ()=>{ mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) - mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(false) + mockLikeOeuvreRepository.doesUserLike = jest.fn().mockReturnValue(false) mockLikeOeuvreRepository.like = jest.fn() const result = await likeOeuvre(1,1,"artist",serviceLocator) expect(result).toBe(true) @@ -62,7 +62,7 @@ describe("like oeuvre use case ", ()=>{ mockAccessTokenManager.decode = jest.fn(()=>{return {value:1}}) mockUserRepository.getByUser = jest.fn().mockReturnValue(mockUser) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockArtist) - mockLikeOeuvreRepository.doesUserLikes = jest.fn().mockReturnValue(true) + mockLikeOeuvreRepository.doesUserLike = jest.fn().mockReturnValue(true) mockLikeOeuvreRepository.unlike = jest.fn() const result = await likeOeuvre(1,1,"artist",serviceLocator) expect(result).toBe(false) diff --git a/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js b/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js index 65d3606..bc561c6 100644 --- a/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js +++ b/test/unit/application/usecase/review/fixture/getOeuvreReviewsFixture.js @@ -39,7 +39,7 @@ const actualDate = new Date() const rawReview = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, @@ -51,7 +51,7 @@ const rawReview = { const expectedReview = { id_review:1, description: "C'est top", - countlikes: 2, + countlike: 2, countComment: 4, note: 5, createdAt: actualDate, diff --git a/test/unit/application/usecase/review/fixture/getReviewFixture.js b/test/unit/application/usecase/review/fixture/getReviewFixture.js index 2b09d51..74dccab 100644 --- a/test/unit/application/usecase/review/fixture/getReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/getReviewFixture.js @@ -22,6 +22,8 @@ const mockUser = { type: "user", is_private: false } + + const mockPublicUser = { pseudo: "John Doe", alias: "John", @@ -34,6 +36,13 @@ const mockPublicUser = { type: "user", is_private: false } +const mockComments = [ + { + id: 298, + description: 'test4', + utilisateur: mockPublicUser + } + ] const mockUserPrivate = { pseudo: "John Doe", alias: "John", @@ -50,7 +59,7 @@ const actualDate = new Date() const rawReview = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, @@ -62,7 +71,7 @@ const rawReview = { const expectedReview = { id_review:1, description: "C'est top", - countlikes: 2, + countlike: 2, countComment: 4, note: 5, createdAt: actualDate, @@ -76,6 +85,7 @@ const expectedReview = { genres: ["Reggae", "Roots"], type: "artist" }, + comments: mockComments, utilisateur: mockPublicUser, type: 'artist', } @@ -85,7 +95,7 @@ const expectedReview = { const rawReviewPrivate = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, @@ -97,7 +107,7 @@ const rawReviewPrivate = { const expectedPrivate = { id_review:1, description: "C'est top", - countlikes: 2, + countlike: 2, countComment: 4, note: 5, createdAt: actualDate, @@ -111,10 +121,11 @@ const expectedPrivate = { genres: ["Reggae", "Roots"], type: "artist" }, + comments: mockComments, utilisateur: mockUserPrivate, type: 'artist', } - + module.exports = { rawReview, @@ -122,5 +133,6 @@ module.exports = { expectedReview, rawReviewPrivate, expectedPrivate, + mockComments, } \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/getReviewsFixture.js b/test/unit/application/usecase/review/fixture/getReviewsFixture.js new file mode 100644 index 0000000..ea1e698 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/getReviewsFixture.js @@ -0,0 +1,76 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlike: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUser +} +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_utilisateur: 1, + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} +const expectedReview = { + id_review:1, + description: "C'est top", + countlike: 2, + countComment: 4, + note: 5, + createdAt: actualDate, + doesUserLike: false, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockPublicUser, + type: 'artist', +} + + +module.exports = { + rawReview, + mockArtist, + expectedReview, +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/getUserReviewFixture.js b/test/unit/application/usecase/review/fixture/getUserReviewFixture.js new file mode 100644 index 0000000..8cb6d92 --- /dev/null +++ b/test/unit/application/usecase/review/fixture/getUserReviewFixture.js @@ -0,0 +1,95 @@ +const mockArtist = { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + images: [{ + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + width: 640 + }], + external_urls: {spotify: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii"}, + popularity: 79, + genres: ["Reggae", "Roots"] +} +const mockUser = { + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + + +const mockPublicUser = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_utilisateur: 1, + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false +} + +const mockUserPrivate = { + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true +} +const actualDate = new Date() +const rawReview = { + id_review: 1, + id_oeuvre: 1, + countlike: 2, + countComment: 4, + description: "C'est top", + note: 5, + createdAt: actualDate, + updatedAt: actualDate, + type: 'artist', + utilisateur: mockUser +} +const expectedReview = { + id_review:1, + description: "C'est top", + countlike: 2, + countComment: 4, + note: 5, + createdAt: actualDate, + doesUserLike: false, + oeuvre: { + id: "32kWZXLpwGm5Y2B0lKb6Ii", + name: "Bob Marley", + image: "https://i.scdn.co/image/ab67616d0000b273c9adfbd773852e286faed040", + spotify_url: "https://open.spotify.com/artist/32kWZXLpwGm5Y2B0lKb6Ii", + popularity: 79, + genres: ["Reggae", "Roots"], + type: "artist" + }, + utilisateur: mockPublicUser, + type: 'artist', +} + + + + + +module.exports = { + rawReview, + mockArtist, + expectedReview, + +} \ No newline at end of file diff --git a/test/unit/application/usecase/review/fixture/putReviewFixture.js b/test/unit/application/usecase/review/fixture/putReviewFixture.js index 177948a..a2da09b 100644 --- a/test/unit/application/usecase/review/fixture/putReviewFixture.js +++ b/test/unit/application/usecase/review/fixture/putReviewFixture.js @@ -50,7 +50,7 @@ const actualDate = new Date() const rawReview = { id_review: 1, id_oeuvre: 1, - countlikes: 2, + countlike: 2, countComment: 4, description: "C'est top", note: 5, @@ -62,7 +62,7 @@ const rawReview = { const expectedReview = { id_review:1, description: "C'est top", - countlikes: 2, + countlike: 2, countComment: 4, note: 5, createdAt: actualDate, diff --git a/test/unit/application/usecase/review/getReview.test.js b/test/unit/application/usecase/review/getReview.test.js index dd15520..e7bbaee 100644 --- a/test/unit/application/usecase/review/getReview.test.js +++ b/test/unit/application/usecase/review/getReview.test.js @@ -7,6 +7,7 @@ const { expectedReview, rawReviewPrivate, expectedPrivate, + mockComments } = require("./fixture/getReviewFixture") @@ -15,18 +16,21 @@ describe("getReview Test", ()=>{ const mockFriendRepository = {} const mockAccesTokenManager = {} const mockSpotifyRepository = {} + const mockCommentRepository = {} const serviceLocator = { reviewRepository: mockReviewRepository, friendRepository: mockFriendRepository, accessTokenManager: mockAccesTokenManager, spotifyRepository: mockSpotifyRepository, + commentRepository: mockCommentRepository } describe("successful cases", () => { it("should return review with artist from repository", async () => { mockReviewRepository.getById = jest.fn((id) => rawReview) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - const result = await getReview(1,undefined, serviceLocator) + mockCommentRepository.getReviewComments = jest.fn().mockReturnValue(mockComments) + const result = await getReview(1,undefined,1,10,true, serviceLocator) console.log(result) expect(result).toEqual(expectedReview) }) @@ -36,7 +40,7 @@ describe("getReview Test", ()=>{ mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) - const result = await getReview(1,'something', serviceLocator) + const result = await getReview(1,'something',1,10,true, serviceLocator) expect(result).toEqual(expectedPrivate) }) @@ -45,7 +49,7 @@ describe("getReview Test", ()=>{ it("should throw review not found error", async () => { mockReviewRepository.getById = jest.fn((id) => null) const error = await catchError(async () => { - await getReview(1,undefined, serviceLocator) + await getReview(1,undefined,1,10,true, serviceLocator) }) console.log(error) expect(error.code).toBe(404) @@ -60,7 +64,7 @@ describe("getReview Test", ()=>{ } mockReviewRepository.getById = jest.fn((id) => mockReview) const error = await catchError(async () => { - await getReview(1,undefined, serviceLocator) + await getReview(1,undefined,1,10,true, serviceLocator) }) expect(error.code).toBe(403) }) @@ -77,7 +81,7 @@ describe("getReview Test", ()=>{ mockFriendRepository.areFriends = jest.fn((id, id_ami) => false) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) const error = await catchError(async () => { - await getReview(1,'something', serviceLocator) + await getReview(1,'something',1,10,true, serviceLocator) }) expect(error.code).toBe(403) expect(mockFriendRepository.areFriends).toHaveBeenCalledTimes(1) @@ -94,7 +98,7 @@ describe("getReview Test", ()=>{ } }) const error = await catchError(async () => { - await getReview(1,undefined, serviceLocator) + await getReview(1,undefined,1,10,true, serviceLocator) }) expect(error.code).toBe(400) diff --git a/test/unit/application/usecase/review/getReviews.test.js b/test/unit/application/usecase/review/getReviews.test.js index 8285fee..39d0d1d 100644 --- a/test/unit/application/usecase/review/getReviews.test.js +++ b/test/unit/application/usecase/review/getReviews.test.js @@ -4,7 +4,7 @@ const { mockArtist, rawReview, expectedReview, -} = require("./fixture/getReviewFixture") +} = require("./fixture/getReviewsFixture") describe("getReviews Test", ()=>{ const mockReviewRepository = {} diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js index 0345ade..74af95f 100644 --- a/test/unit/application/usecase/review/getUserReviews.test.js +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -4,7 +4,7 @@ const { mockArtist, rawReview, expectedReview, -} = require("./fixture/getReviewFixture") +} = require("./fixture/getUserReviewFixture") describe("getReviews Test", ()=>{ const mockReviewRepository = {} diff --git a/test/unit/application/usecase/review/likeReview.test.js b/test/unit/application/usecase/review/likeReview.test.js index a0f2097..8be531a 100644 --- a/test/unit/application/usecase/review/likeReview.test.js +++ b/test/unit/application/usecase/review/likeReview.test.js @@ -19,7 +19,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLikes = jest.fn((id) => true) + mockReviewRepository.doesUserLike = jest.fn((id) => true) mockReviewRepository.unlikeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.unlikeReview).toHaveBeenCalledTimes(1) @@ -32,7 +32,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLikes = jest.fn((id) => false) + mockReviewRepository.doesUserLike = jest.fn((id) => false) mockReviewRepository.likeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.likeReview).toHaveBeenCalledTimes(1) From 18ff20c011d53e1793c54ad8ecdf2990c4c056dd Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Wed, 17 Apr 2024 23:26:45 +0200 Subject: [PATCH 53/67] renvoiyer les infos utlisateur lors d'une confirmation utilisateur --- lib/application/use_cases/user/CompleteAccount.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/application/use_cases/user/CompleteAccount.js b/lib/application/use_cases/user/CompleteAccount.js index f9dcac4..5a5cdc6 100644 --- a/lib/application/use_cases/user/CompleteAccount.js +++ b/lib/application/use_cases/user/CompleteAccount.js @@ -2,6 +2,8 @@ const throwStatusCode = require("../utils/throwStatusCode") +const UserPublic = require("../../../domain/model/UserPublic") + module.exports = async (pseudo,alias, bio, photo,confirmToken, { userRepository,documentRepository, accessTokenManager }) => { const userTest = await userRepository.getByEmailOrPseudo(pseudo,pseudo) if(userTest){ @@ -24,7 +26,7 @@ module.exports = async (pseudo,alias, bio, photo,confirmToken, { userRepository, } await userRepository.updateUser(user) return { - email: user.email, + user: new UserPublic(user), token: accessTokenManager.generate(user) } From c04981729046eef5d7f0b42df831897f8afb6d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:21:13 +0200 Subject: [PATCH 54/67] correction likeOeuvre (#50) --- lib/application/use_cases/oeuvre/likeOeuvre.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/application/use_cases/oeuvre/likeOeuvre.js b/lib/application/use_cases/oeuvre/likeOeuvre.js index 37e1d05..2e12fed 100644 --- a/lib/application/use_cases/oeuvre/likeOeuvre.js +++ b/lib/application/use_cases/oeuvre/likeOeuvre.js @@ -4,7 +4,7 @@ module.exports = async (userToken, artistId,type, {userRepository,likeOeuvreRepo if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") const oeuvre = spotifyRepository.getOeuvre(artistId,type) if(oeuvre.error) throwStatusCode(oeuvre.error.status,oeuvre.error.message) - if(! await likeOeuvreRepository.doesUserLike(id_utilisateur,artistId)) { + if(! await likeOeuvreRepository.doesUserLikes(id_utilisateur,artistId)) { await likeOeuvreRepository.like(id_utilisateur,artistId) return true } From ce342654cc7a292acde608a668c375ea7e5ca942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:21:27 +0200 Subject: [PATCH 55/67] fix getComment (#51) --- lib/application/use_cases/comment/getComment.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/application/use_cases/comment/getComment.js b/lib/application/use_cases/comment/getComment.js index 91c95e9..1b09465 100644 --- a/lib/application/use_cases/comment/getComment.js +++ b/lib/application/use_cases/comment/getComment.js @@ -2,6 +2,8 @@ const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializ const ReviewPublic = require("../../../domain/model/ReviewPublic"); const PublicComment = require("../../../domain/model/PublicComment"); const UserPublic = require("../../../domain/model/UserPublic"); +const throwStatusCode = require("../utils/throwStatusCode"); + module.exports = async ( id_com, From a938760ce28bf59e32dfada6b6046e2cc755b1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:21:34 +0200 Subject: [PATCH 56/67] Feature/fix review (#52) * fix getComment * non prise en compte des commentaires deleted --- lib/infrastructure/repositories/ReviewRepository.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js index 2f2f9bc..dc6de58 100644 --- a/lib/infrastructure/repositories/ReviewRepository.js +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -166,7 +166,7 @@ module.exports = class extends ReviewRepository { 'countLike' ], [ - sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), + sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review AND commentaire.deleted = 0)'), 'countComment' ], ] From d4a8944f0435a8892df9e73e435380e121390b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:21:42 +0200 Subject: [PATCH 57/67] Feature/followers artist (#53) * fix getComment * non prise en compte des commentaires deleted * get artist's followers without tests * FIX get artist's followers --- .../use_cases/artist/getArtistFollowers.js | 21 ++++++++++ lib/domain/model/ArtistFollowersPage.js | 6 +++ .../repositories/FollowRepository.js | 41 ++++++++++++++++++- .../controllers/ArtistController.js | 14 ++++++- lib/interfaces/routes/artist.js | 27 ++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 lib/application/use_cases/artist/getArtistFollowers.js create mode 100644 lib/domain/model/ArtistFollowersPage.js diff --git a/lib/application/use_cases/artist/getArtistFollowers.js b/lib/application/use_cases/artist/getArtistFollowers.js new file mode 100644 index 0000000..1904ee1 --- /dev/null +++ b/lib/application/use_cases/artist/getArtistFollowers.js @@ -0,0 +1,21 @@ +const throwStatusCode = require("../utils/throwStatusCode.js"); +const ArtistFollowersPage = require("../../../domain/model/ArtistFollowersPage.js"); + +module.exports = async (artistId, userToken, {accessTokenManager,userRepository, spotifyRepository, followRepository, friendRepository}) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value + const user = await userRepository.getByUser(id_utilisateur) + if(!user) throwStatusCode(401,"votre token d'authentification n'est pas le bon") + + const artist = await spotifyRepository.getSpotifyArtist(artistId) + if(artist.error) + throwStatusCode(artist.error.status,artist.error.message) + + const followersFriendsList = await followRepository.getFriendsFollowing(artistId,id_utilisateur) + const followersWithoutFriends = await followRepository.getArtistFollowersWithoutFriends(artistId, id_utilisateur) + //const allFollowers = [...followersFriendsList, ...followersWithoutFriends]; + const allFollowers = { + "followersFriendsList": followersFriendsList, + "followersWithoutFriends": followersWithoutFriends + } + return new ArtistFollowersPage(artist, allFollowers) +} \ No newline at end of file diff --git a/lib/domain/model/ArtistFollowersPage.js b/lib/domain/model/ArtistFollowersPage.js new file mode 100644 index 0000000..503d224 --- /dev/null +++ b/lib/domain/model/ArtistFollowersPage.js @@ -0,0 +1,6 @@ +module.exports = class { + constructor(artist,allFollowers){ + this.artist = artist + this.allFollowers = allFollowers + } +} \ No newline at end of file diff --git a/lib/infrastructure/repositories/FollowRepository.js b/lib/infrastructure/repositories/FollowRepository.js index 29f8f59..cfcbf85 100644 --- a/lib/infrastructure/repositories/FollowRepository.js +++ b/lib/infrastructure/repositories/FollowRepository.js @@ -88,7 +88,12 @@ module.exports = class extends FollowRepository { } ], where: { - id_utilisateur: {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser}))`)} + + [Op.or]: [ + sequelize.literal(` utilisateur.id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${idUser} AND en_attente = false )`), + sequelize.literal(`utilisateur.id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser} AND en_attente = false )`) + ] + } }) @@ -109,11 +114,43 @@ module.exports = class extends FollowRepository { } ], where: { - id_utilisateur: {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = :idUser))`)} + //id_utilisateur: {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = :idUser))`)} + [Op.or]: [ + sequelize.literal(` utilisateur.id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${idUser} AND en_attente = false )`), + sequelize.literal(`utilisateur.id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser} AND en_attente = false )`) + ] }, replacements: { idUser }, }) return countUsers } + async getArtistFollowersWithoutFriends(idArtist,idUser,limit) { + //affiche idUser s'il follow l'artiste + const users = await this.userModel.findAll({ + limit, + include: [ + { + model: this.artistModel, + as: 'utilisateur_m_n', + required: true, + through: { + where: { + id_artiste: idArtist + } + } + } + ], + where: { + //id_utilisateur: {[Op.notIn]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser}))`)} + [Op.and]: [ + sequelize.literal(`utilisateur.id_utilisateur not IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${idUser} AND en_attente = false)`), + sequelize.literal(`utilisateur.id_utilisateur not IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${idUser} AND en_attente = false)`), + + ] + } + }) + + return users.map(item => new User(item)) + } }; diff --git a/lib/interfaces/controllers/ArtistController.js b/lib/interfaces/controllers/ArtistController.js index dc26fdb..17c0765 100644 --- a/lib/interfaces/controllers/ArtistController.js +++ b/lib/interfaces/controllers/ArtistController.js @@ -1,6 +1,7 @@ 'use strict'; const getArtist = require('../../application/use_cases/artist/getArtist'); +const getArtistFollowers = require('../../application/use_cases/artist/getArtistFollowers'); const handleError = require("./utils/handleError"); module.exports = { @@ -17,6 +18,17 @@ module.exports = { return handleError(e) } }, - + async getFollowers(request, handler){ + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(' '); + const {id} = request.params + try{ + return handler.response(await getArtistFollowers(id,token, serviceLocator)).code(200) + } + catch (e){ + return handleError(e) + } + } } diff --git a/lib/interfaces/routes/artist.js b/lib/interfaces/routes/artist.js index c066162..754dc7a 100644 --- a/lib/interfaces/routes/artist.js +++ b/lib/interfaces/routes/artist.js @@ -34,6 +34,33 @@ module.exports = { } }, }, + { + method: 'GET', + path: '/artistFollowers/{id}', + handler: ArtistController.getFollowers, + options: { + auth: 'jwt', + description: 'get artist followers', + tags: ['api'], + plugins: { + 'hapi-swagger': { + responses: { + 200: {description : 'Success'}, + 204: {description : 'No content'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description : 'Internal server error'}, + 502: {description : 'bad gateway'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + params: getArtist + } + }, + }, ]); } }; \ No newline at end of file From b0e99a30977454f4a6983fc6b1b93ce98f619e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:26:40 +0200 Subject: [PATCH 58/67] Feature/friend reviews (#54) * added friendsOnly made_by_friend * added friendsOnly made_by_friend --- lib/application/use_cases/artist/getArtist.js | 203 +++--- .../use_cases/comment/getComment.js | 22 +- lib/application/use_cases/oeuvre/getOeuvre.js | 144 ++-- .../use_cases/review/getArtistReviews.js | 73 +- .../use_cases/review/getOeuvreReviews.js | 61 +- lib/application/use_cases/review/getReview.js | 64 +- .../use_cases/review/getReviews.js | 51 +- .../use_cases/review/getUserReviews.js | 78 ++- .../use_cases/review/putComment.js | 1 - lib/application/use_cases/review/putReview.js | 62 +- .../use_cases/review/util/getReview.js | 38 +- lib/domain/entity/ReviewEntity.js | 166 +++-- lib/domain/model/ArtistPage.js | 27 +- lib/domain/model/ReviewPublic.js | 31 +- lib/infrastructure/orm/sequelize/sequelize.js | 228 +++---- .../repositories/CommentRepository.js | 10 +- .../repositories/ReviewRepository.js | 630 ++++++++++-------- .../controllers/ReviewController.js | 376 ++++++----- lib/interfaces/routes/review.js | 566 ++++++++-------- .../serializers/ReviewSerializer.js | 72 +- 20 files changed, 1619 insertions(+), 1284 deletions(-) diff --git a/lib/application/use_cases/artist/getArtist.js b/lib/application/use_cases/artist/getArtist.js index be5c69e..31eba13 100644 --- a/lib/application/use_cases/artist/getArtist.js +++ b/lib/application/use_cases/artist/getArtist.js @@ -4,89 +4,142 @@ const UserPublic = require("../../../domain/model/UserPublic"); const ArtistPage = require("../../../domain/model/ArtistPage.js"); const serializeAlbum = require("../../../interfaces/serializers/AlbumSerializer"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer.js"); -module.exports = async (artistId, userToken, {accessTokenManager,userRepository, spotifyRepository, reviewRepository,followRepository}) => { - const id_utilisateur = accessTokenManager.decode(userToken)?.value - const user = await userRepository.getByUser(id_utilisateur) - if(!user) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - const artist = await spotifyRepository.getSpotifyArtist(artistId) - if(artist.error) - throwStatusCode(artist.error.status,artist.error.message) - const doesUserFollow = await followRepository.doesFollows(id_utilisateur,artistId) - const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single') - - const album_ids = albums.items.map((album)=>album.id) - const [ - follower_count, - friends_followers, - friend_follower_count, - reviewsByLike, - reviewsByTime, - ] = await Promise.all([ - followRepository.getFollowersCount(artistId), - followRepository.getFriendsFollowing(artistId,user.id_utilisateur,3), - followRepository.getFriendsFollowingCount(artistId,user.id_utilisateur), - reviewRepository.getOeuvreReviews(1,3,true,false,album_ids,user.id_utilisateur), - reviewRepository.getOeuvreReviews(1,3,false,false,album_ids,user.id_utilisateur), - ]) - return await artistSerizilizer( - user.id_utilisateur, - doesUserFollow, - artist, - albums, - follower_count, - friends_followers, - friend_follower_count, - reviewsByLike, - reviewsByTime, - {reviewRepository,spotifyRepository} - ) -} - -const artistSerizilizer = async ( +module.exports = async ( + artistId, + userToken, + { + accessTokenManager, + userRepository, + spotifyRepository, + reviewRepository, + followRepository, + friendRepository, + } +) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value; + const user = await userRepository.getByUser(id_utilisateur); + if (!user) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + const artist = await spotifyRepository.getSpotifyArtist(artistId); + if (artist.error) throwStatusCode(artist.error.status, artist.error.message); + const doesUserFollow = await followRepository.doesFollows( id_utilisateur, + artistId + ); + const albums = await spotifyRepository.getSpotifyArtistSongs( + artistId, + "album,single" + ); + + const album_ids = albums.items.map((album) => album.id); + const [ + follower_count, + friends_followers, + friend_follower_count, + reviewsByLike, + reviewsByTime, + reviewsByFriends, + ] = await Promise.all([ + followRepository.getFollowersCount(artistId), + followRepository.getFriendsFollowing(artistId, user.id_utilisateur, 3), + followRepository.getFriendsFollowingCount(artistId, user.id_utilisateur), + reviewRepository.getOeuvreReviews( + 1, + 3, + true, + false, + false, + album_ids, + user.id_utilisateur + ), + reviewRepository.getOeuvreReviews( + 1, + 3, + false, + false, + false, + album_ids, + user.id_utilisateur + ), + reviewRepository.getOeuvreReviews( + 1, + 3, + false, + false, + true, + album_ids, + user.id_utilisateur + ), + ]); + return await artistSerizilizer( + user.id_utilisateur, doesUserFollow, artist, albums, - followers_count, + follower_count, friends_followers, friend_follower_count, reviewsByLike, reviewsByTime, - {reviewRepository,spotifyRepository}) => { - artist.follower_count = followers_count - artist = serializeArtiste(artist) + reviewsByFriends, + { reviewRepository, spotifyRepository, friendRepository } + ); +}; - albums = await Promise.all(albums.items.map(async (item) => { - item.rating = await reviewRepository.getOeuvreRating(item.id) - item.reviewCount = await reviewRepository.getReviewCount(item.id) - return serializeAlbum(item) - })) +const artistSerizilizer = async ( + id_utilisateur, + doesUserFollow, + artist, + albums, + followers_count, + friends_followers, + friend_follower_count, + reviewsByLike, + reviewsByTime, + reviewsByFriends, + { reviewRepository, spotifyRepository, friendRepository } +) => { + artist.follower_count = followers_count; + artist = serializeArtiste(artist); + + albums = await Promise.all( + albums.items.map(async (item) => { + item.rating = await reviewRepository.getOeuvreRating(item.id); + item.reviewCount = await reviewRepository.getReviewCount(item.id); + return serializeAlbum(item); + }) + ); - friends_followers = { - count: friend_follower_count, - users: friends_followers.map(item => { - return new UserPublic(item) - }) - } - - reviewsByLike = await Promise.all( - reviewsByLike.map(async (review) => { - const doesUserLike = await reviewRepository.doesUserLike( id_utilisateur,review.id_review) - const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLike) - }) - ) + friends_followers = { + count: friend_follower_count, + users: friends_followers.map((item) => { + return new UserPublic(item); + }), + }; + const serializeReviews = (reviews) => { + return reviews.map(async (review) => { + return reviewSerializer( + review, + id_utilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ); + }); + }; + reviewsByLike = await Promise.all(serializeReviews(reviewsByLike)); - reviewsByTime = await Promise.all( - reviewsByTime.map(async (review) => { - const doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,review.id_review) - const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLike) - }) - ) - return new ArtistPage(artist,albums,friends_followers,reviewsByLike,reviewsByTime,doesUserFollow) -} \ No newline at end of file + reviewsByTime = await Promise.all(serializeReviews(reviewsByTime)); + + reviewsByFriends = await Promise.all(serializeReviews(reviewsByFriends)); + return new ArtistPage( + artist, + albums, + friends_followers, + reviewsByLike, + reviewsByTime, + reviewsByFriends, + doesUserFollow + ); +}; diff --git a/lib/application/use_cases/comment/getComment.js b/lib/application/use_cases/comment/getComment.js index 1b09465..45082b3 100644 --- a/lib/application/use_cases/comment/getComment.js +++ b/lib/application/use_cases/comment/getComment.js @@ -1,10 +1,8 @@ const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -const ReviewPublic = require("../../../domain/model/ReviewPublic"); const PublicComment = require("../../../domain/model/PublicComment"); const UserPublic = require("../../../domain/model/UserPublic"); const throwStatusCode = require("../utils/throwStatusCode"); - module.exports = async ( id_com, page, @@ -59,17 +57,6 @@ const serialize = async ( { spotifyRepository, friendRepository, commentRepository, reviewRepository } ) => { const serializeReview = async () => { - const rawOeuvre = await spotifyRepository.getOeuvre( - review.id_oeuvre, - review.type - ); - if (rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); - const doesUserLike = await reviewRepository.doesUserLike( - id_utilisateur, - review.id_review - ); - return !review.utilisateur.is_private || friendRepository.areFriends( id_utilisateur, @@ -77,10 +64,11 @@ const serialize = async ( ) ? reviewSerializer( review, - rawOeuvre, - review.utilisateur, - doesUserLike, - comments + id_utilisateur, + comments, + spotifyRepository, + reviewRepository, + friendRepository ) : { private: true, diff --git a/lib/application/use_cases/oeuvre/getOeuvre.js b/lib/application/use_cases/oeuvre/getOeuvre.js index 97346a5..fe1c139 100644 --- a/lib/application/use_cases/oeuvre/getOeuvre.js +++ b/lib/application/use_cases/oeuvre/getOeuvre.js @@ -5,60 +5,108 @@ const fetchArtist = require("../spotify/FetchArtist.js"); const getAlbum = require("../spotify/getAlbum.js"); const getTrack = require("../spotify/getTrack.js"); +module.exports = async ( + idOeuvre, + userToken, + { + accessTokenManager, + userRepository, + spotifyRepository, + reviewRepository, + likeOeuvreRepository, + oeuvreFavRepository, + friendRepository, + } +) => { + const idUtilisateur = accessTokenManager.decode(userToken)?.value; + const user = await userRepository.getByUser(idUtilisateur); + if (!user) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); -module.exports = async (idOeuvre, userToken, {accessTokenManager,userRepository, spotifyRepository, reviewRepository,likeOeuvreRepository, oeuvreFavRepository}) => { - const idUtilisateur = accessTokenManager.decode(userToken)?.value - const user = await userRepository.getByUser(idUtilisateur) - if(!user) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - - let oeuvre + let oeuvre; + try { + oeuvre = await getAlbum(idOeuvre, { spotifyRepository }); + } catch (errorAlbum) { try { - oeuvre = await getAlbum(idOeuvre, {spotifyRepository}); - } catch (errorAlbum) { - try { - oeuvre = await getTrack(idOeuvre, {spotifyRepository}); - } catch (errorTrack) { - - throwStatusCode(404, "L'ID de l'oeuvre est introuvable"); - } + oeuvre = await getTrack(idOeuvre, { spotifyRepository }); + } catch (errorTrack) { + throwStatusCode(404, "L'ID de l'oeuvre est introuvable"); } - - const artistIds = oeuvre.artists.map((artist)=>artist.id) + } + + const artistIds = oeuvre.artists.map((artist) => artist.id); + + const artists = await Promise.all( + artistIds.map(async (id) => { + return await fetchArtist(id, { spotifyRepository }); + }) + ); + + const doesUserLikes = await likeOeuvreRepository.doesUserLikes( + idUtilisateur, + idOeuvre + ); + const doesUserFav = await oeuvreFavRepository.oeuvreFavExists( + idUtilisateur, + idOeuvre + ); - const artists = await Promise.all(artistIds.map(async (id) => { - return await fetchArtist(id, { spotifyRepository }); - })); - - const doesUserLikes = await likeOeuvreRepository.doesUserLikes(idUtilisateur,idOeuvre) - const doesUserFav = await oeuvreFavRepository.oeuvreFavExists(idUtilisateur,idOeuvre) - - oeuvre.likeCount = await likeOeuvreRepository.getLikeCount(idOeuvre) - oeuvre.reviewCount = await reviewRepository.getReviewCount(idOeuvre) - oeuvre.rating = await reviewRepository.getOeuvreRating(idOeuvre) + oeuvre.likeCount = await likeOeuvreRepository.getLikeCount(idOeuvre); + oeuvre.reviewCount = await reviewRepository.getReviewCount(idOeuvre); + oeuvre.rating = await reviewRepository.getOeuvreRating(idOeuvre); - const reviewsByLike = await reviewRepository.getOeuvreReviews(1,3,true,false,idOeuvre,idUtilisateur) - const reviewsByTime = await reviewRepository.getOeuvreReviews(1,3,false,false,idOeuvre,idUtilisateur) + const reviewsByLike = await reviewRepository.getOeuvreReviews( + 1, + 3, + true, + false, + false, + [idOeuvre], + idUtilisateur + ); + const reviewsByTime = await reviewRepository.getOeuvreReviews( + 1, + 3, + false, + false, + false, + [idOeuvre], + idUtilisateur + ); - const reviewsByLikeSeri = await Promise.all( - reviewsByLike.map(async (review) => { - const doesUserLikeReview = await reviewRepository.doesUserLike(idUtilisateur,review.id_review) - const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLikeReview) - }) - ) + const reviewsByLikeSeri = await Promise.all( + reviewsByLike.map(async (review) => { + return reviewSerializer( + review, + idUtilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ); + }) + ); - const reviewsByTimeSeri = await Promise.all( - reviewsByTime.map(async (review) => { - const doesUserLikeReview = await reviewRepository.doesUserLike(idUtilisateur,review.id_review) - const rawOeuvre = await spotifyRepository.getOeuvre(review.id_oeuvre,review.type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(review,rawOeuvre,review.utilisateur,doesUserLikeReview) - }) - ) + const reviewsByTimeSeri = await Promise.all( + reviewsByTime.map(async (review) => { + return reviewSerializer( + review, + idUtilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ); + }) + ); - return new OeuvrePage(oeuvre, artists, reviewsByLikeSeri, reviewsByTimeSeri, doesUserLikes, doesUserFav) -} \ No newline at end of file + return new OeuvrePage( + oeuvre, + artists, + reviewsByLikeSeri, + reviewsByTimeSeri, + doesUserLikes, + doesUserFav + ); +}; diff --git a/lib/application/use_cases/review/getArtistReviews.js b/lib/application/use_cases/review/getArtistReviews.js index c67377e..038bccb 100644 --- a/lib/application/use_cases/review/getArtistReviews.js +++ b/lib/application/use_cases/review/getArtistReviews.js @@ -1,31 +1,46 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -module.exports = async (artistId, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager}) => { - let id = null - if (userToken) { - id = accessTokenManager.decode(userToken)?.value - } - const artist = await spotifyRepository.getSpotifyArtist(artistId) - if(artist.error) - throwStatusCode(artist.error.status,artist.error.message) - const albums = await spotifyRepository.getSpotifyArtistSongs(artistId,'album,single') - const album_ids = albums.items.map((album)=>album.id) - const reviews = await reviewRepository.getOeuvreReviews(page,pageSize, orderByLike,false,album_ids,id) - const serializedReviews = [] - reviews.forEach(element => { - - serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) - }) - return await Promise.all(serializedReviews) -} - - -const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { - let doesUserLike = false - if(id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) -} \ No newline at end of file +module.exports = async ( + artistId, + userToken, + page, + pageSize, + orderByLike, + friendsOnly, + { reviewRepository, spotifyRepository, accessTokenManager, friendRepository } +) => { + let id = null; + if (userToken) { + id = accessTokenManager.decode(userToken)?.value; + } + const artist = await spotifyRepository.getSpotifyArtist(artistId); + if (artist.error) throwStatusCode(artist.error.status, artist.error.message); + const albums = await spotifyRepository.getSpotifyArtistSongs( + artistId, + "album,single" + ); + const album_ids = albums.items.map((album) => album.id); + const reviews = await reviewRepository.getOeuvreReviews( + page, + pageSize, + orderByLike, + false, + friendsOnly, + album_ids, + id + ); + const serializedReviews = []; + reviews.forEach((element) => { + serializedReviews.push( + reviewSerializer( + element, + id, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ) + ); + }); + return await Promise.all(serializedReviews); +}; diff --git a/lib/application/use_cases/review/getOeuvreReviews.js b/lib/application/use_cases/review/getOeuvreReviews.js index 230fb00..ee1db08 100644 --- a/lib/application/use_cases/review/getOeuvreReviews.js +++ b/lib/application/use_cases/review/getOeuvreReviews.js @@ -1,26 +1,39 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -module.exports = async (OeuvreId, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager}) => { - let id = null - if (userToken) { - id = accessTokenManager.decode(userToken)?.value - } - const reviews = await reviewRepository.getOeuvreReviews(page,pageSize, orderByLike,false,[OeuvreId],id) - const serializedReviews = [] - reviews.forEach(element => { - - serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) - }) - return await Promise.all(serializedReviews) -} - - -const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { - let doesUserLike = false - if(id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - if(rawOeuvre.error) throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) -} \ No newline at end of file +module.exports = async ( + OeuvreId, + userToken, + page, + pageSize, + orderByLike, + friendsOnly, + { reviewRepository, spotifyRepository, accessTokenManager, friendRepository } +) => { + let id = null; + if (userToken) { + id = accessTokenManager.decode(userToken)?.value; + } + const reviews = await reviewRepository.getOeuvreReviews( + page, + pageSize, + orderByLike, + false, + friendsOnly, + [OeuvreId], + id + ); + const serializedReviews = []; + reviews.forEach((element) => { + serializedReviews.push( + reviewSerializer( + element, + id, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ) + ); + }); + return await Promise.all(serializedReviews); +}; diff --git a/lib/application/use_cases/review/getReview.js b/lib/application/use_cases/review/getReview.js index 4ac5b80..0f6b47d 100644 --- a/lib/application/use_cases/review/getReview.js +++ b/lib/application/use_cases/review/getReview.js @@ -1,21 +1,51 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -const getReview = require("./util/getReview") -module.exports = async (idReview,userToken,page,pageSize,orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,commentRepository}) => { +const getReview = require("./util/getReview"); +module.exports = async ( + idReview, + userToken, + page, + pageSize, + orderByLike, + { + reviewRepository, + spotifyRepository, + accessTokenManager, + friendRepository, + commentRepository, + } +) => { + const rawReview = await getReview(idReview, userToken, { + accessTokenManager, + friendRepository, + reviewRepository, + }); + console.log(rawReview); + const rawOeuvre = await spotifyRepository.getOeuvre( + rawReview.id_oeuvre, + rawReview.type + ); + if (rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); - const rawReview = await getReview(idReview,userToken, {accessTokenManager,friendRepository,reviewRepository}) - const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) + let id_utilisateur = userToken + ? accessTokenManager.decode(userToken)?.value + : null; + const comments = await commentRepository.getReviewComments( + rawReview.id_review, + id_utilisateur, + false, + page, + pageSize, + orderByLike + ); - - let doesUserLike = false - let id_utilisateur = null - if(userToken) { - id_utilisateur = accessTokenManager.decode(userToken)?.value - doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - const comments = await commentRepository.getReviewComments(rawReview.id_review,id_utilisateur,false,page,pageSize,orderByLike) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike,comments) - -} \ No newline at end of file + return reviewSerializer( + rawReview, + id_utilisateur, + comments, + spotifyRepository, + reviewRepository, + friendRepository + ); +}; diff --git a/lib/application/use_cases/review/getReviews.js b/lib/application/use_cases/review/getReviews.js index 015a81e..a3e1a14 100644 --- a/lib/application/use_cases/review/getReviews.js +++ b/lib/application/use_cases/review/getReviews.js @@ -1,20 +1,33 @@ const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -module.exports = async (page,pageSize,orderByLike,userToken,{reviewRepository, spotifyRepository, accessTokenManager}) => { - if(userToken) - userToken = accessTokenManager.decode(userToken)?.value - const rawReviews = await reviewRepository.getReviews(page,pageSize,orderByLike, false,userToken) - const serializedReviews = [] - rawReviews.forEach(element => { - serializedReviews.push(serializeReview(element,userToken,spotifyRepository,reviewRepository)) - }) - return await Promise.all(serializedReviews) -} - -const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { - let doesUserLike = false - if(id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) -} \ No newline at end of file +module.exports = async ( + page, + pageSize, + orderByLike, + friendsOnly, + userToken, + { reviewRepository, spotifyRepository, accessTokenManager, friendRepository } +) => { + if (userToken) userToken = accessTokenManager.decode(userToken)?.value; + const rawReviews = await reviewRepository.getReviews( + page, + pageSize, + orderByLike, + false, + friendsOnly, + userToken + ); + const serializedReviews = []; + rawReviews.forEach((element) => { + serializedReviews.push( + reviewSerializer( + element, + userToken, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ) + ); + }); + return await Promise.all(serializedReviews); +}; diff --git a/lib/application/use_cases/review/getUserReviews.js b/lib/application/use_cases/review/getUserReviews.js index 062ddbd..6487bd2 100644 --- a/lib/application/use_cases/review/getUserReviews.js +++ b/lib/application/use_cases/review/getUserReviews.js @@ -1,34 +1,50 @@ const throwStatusCode = require("../utils/throwStatusCode"); const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); -module.exports = async (pseudo, userToken,page,pageSize, orderByLike, {reviewRepository, spotifyRepository,accessTokenManager,friendRepository,userRepository}) => { - const testUsers = await userRepository.getByEmailOrPseudo(pseudo,pseudo) - if(!testUsers) throwStatusCode(404,"l'utilisateur n'existe pas") - let id = null - if (testUsers?.is_private) { - let valid = false - if (userToken) { - id = accessTokenManager.decode(userToken)?.value - if (await friendRepository.areFriends(id, testUsers.id_utilisateur)) - valid = true - } - if (!valid) - throwStatusCode(403, "l'utilisateur est en privé") +const { identity } = require("underscore"); +module.exports = async ( + pseudo, + userToken, + page, + pageSize, + orderByLike, + { + reviewRepository, + spotifyRepository, + accessTokenManager, + friendRepository, + userRepository, + } +) => { + const testUsers = await userRepository.getByEmailOrPseudo(pseudo, pseudo); + if (!testUsers) throwStatusCode(404, "l'utilisateur n'existe pas"); + let id = null; + if (testUsers?.is_private) { + let valid = false; + if (userToken) { + id = accessTokenManager.decode(userToken)?.value; + if (await friendRepository.areFriends(id, testUsers.id_utilisateur)) + valid = true; } - const reviews = await reviewRepository.getReviewByUserId(testUsers.id_utilisateur,page,pageSize, orderByLike) - const serializedReviews = [] - reviews.forEach(element => { - - serializedReviews.push(serializeReview(element,id,spotifyRepository,reviewRepository)) - }) - return await Promise.all(serializedReviews) -} - - -const serializeReview = async (rawReview,id_utilisateur,spotifyRepository,reviewRepository) => { - let doesUserLike = false - if(id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike(id_utilisateur,rawReview.id_review) - } - const rawOeuvre = await spotifyRepository.getOeuvre(rawReview.id_oeuvre,rawReview.type) - return reviewSerializer(rawReview,rawOeuvre,rawReview.utilisateur,doesUserLike) -} \ No newline at end of file + if (!valid) throwStatusCode(403, "l'utilisateur est en privé"); + } + const reviews = await reviewRepository.getReviewByUserId( + testUsers.id_utilisateur, + page, + pageSize, + orderByLike + ); + const serializedReviews = []; + reviews.forEach((element) => { + serializedReviews.push( + reviewSerializer( + element, + id, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ) + ); + }); + return await Promise.all(serializedReviews); +}; diff --git a/lib/application/use_cases/review/putComment.js b/lib/application/use_cases/review/putComment.js index 48abdf0..bae5c72 100644 --- a/lib/application/use_cases/review/putComment.js +++ b/lib/application/use_cases/review/putComment.js @@ -1,5 +1,4 @@ const throwStatusCode = require("../utils/throwStatusCode"); -const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); const getReview = require("./util/getReview"); module.exports = async ( userToken, diff --git a/lib/application/use_cases/review/putReview.js b/lib/application/use_cases/review/putReview.js index fc9a769..1ae6ba4 100644 --- a/lib/application/use_cases/review/putReview.js +++ b/lib/application/use_cases/review/putReview.js @@ -1,22 +1,44 @@ const throwStatusCode = require("../utils/throwStatusCode"); -const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer") -module.exports = async (idOeuvre, userToken, description, note,type, {accessTokenManager, userRepository,reviewRepository,spotifyRepository,followRepository})=> { - const id_utilisateur = accessTokenManager.decode(userToken)?.value +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async ( + idOeuvre, + userToken, + description, + note, + type, + { + accessTokenManager, + userRepository, + reviewRepository, + spotifyRepository, + friendRepository, + } +) => { + const id_utilisateur = accessTokenManager.decode(userToken)?.value; - if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - if(await reviewRepository.getByUserAndId(idOeuvre,id_utilisateur)) throwStatusCode(403,"vous avez déjà posté une review") - const id_type_review = await reviewRepository.getTypeReviewID(type) - if(!id_type_review) throwStatusCode(404,"ce type de review n'existe pas") - const rawOeuvre = await spotifyRepository.getOeuvre(idOeuvre,type) - if(rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status,rawOeuvre.error.message) - const ReviewRaw = { - id_oeuvre: idOeuvre, - id_utilisateur, - description, - note, - id_type_review - } - const review = await reviewRepository.persist(ReviewRaw) - return reviewSerializer(review,rawOeuvre,review.utilisateur,false) -} \ No newline at end of file + if (!(await userRepository.getByUser(id_utilisateur))) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + if (await reviewRepository.getByUserAndId(idOeuvre, id_utilisateur)) + throwStatusCode(403, "vous avez déjà posté une review"); + const id_type_review = await reviewRepository.getTypeReviewID(type); + if (!id_type_review) throwStatusCode(404, "ce type de review n'existe pas"); + const rawOeuvre = await spotifyRepository.getOeuvre(idOeuvre, type); + if (rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); + const ReviewRaw = { + id_oeuvre: idOeuvre, + id_utilisateur, + description, + note, + id_type_review, + }; + const review = await reviewRepository.persist(ReviewRaw); + return reviewSerializer( + review, + id_utilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository + ); +}; diff --git a/lib/application/use_cases/review/util/getReview.js b/lib/application/use_cases/review/util/getReview.js index 896b171..8998515 100644 --- a/lib/application/use_cases/review/util/getReview.js +++ b/lib/application/use_cases/review/util/getReview.js @@ -1,18 +1,26 @@ const throwStatusCode = require("../../utils/throwStatusCode"); -module.exports = async(idReview,userToken,{accessTokenManager,friendRepository,reviewRepository}) => { - const rawReview = await reviewRepository.getById(idReview) - if(!rawReview) - throwStatusCode(404,"la review n'existe pas") - if(rawReview.utilisateur.is_private) { - let valid = false - if(userToken) { - const id = accessTokenManager.decode(userToken)?.value - if(await friendRepository.areFriends(id,rawReview.utilisateur.id_utilisateur)) - valid = true - } - if(!valid) - throwStatusCode(403, "l'utilisateur est en privé") +module.exports = async ( + idReview, + userToken, + { accessTokenManager, friendRepository, reviewRepository } +) => { + const rawReview = await reviewRepository.getById(idReview); + if (!rawReview) throwStatusCode(404, "la review n'existe pas"); + + if (userToken) { + const id = accessTokenManager.decode(userToken)?.value; + if ( + await friendRepository.areFriends( + id, + rawReview.utilisateur.id_utilisateur + ) + ) { + return rawReview; } - return rawReview -} \ No newline at end of file + } + if (!rawReview.utilisateur.is_private) { + return rawReview; + } + throwStatusCode(403, "l'utilisateur est en privé"); +}; diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index 9d33b03..5fcdd70 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -1,115 +1,103 @@ -const Joi = require('joi') +const Joi = require("joi"); const putReview = Joi.object().keys({ - idOeuvre: Joi - .string() - .min(1) - .max(50) - .required(), - description: Joi - .string() - .min(1) - .max(1500) - .required(), - note: Joi - .number() - .integer() - .greater(-1) - .less(6) - .required(), - type: Joi - .string() - .required() - .custom((value, helpers) => { - const allowedValues = ["track", "album", "artist","single","compilation"] - return allowedValues.includes(value) ? value : helpers.error('any.invalid') - }) -}) + idOeuvre: Joi.string().min(1).max(50).required(), + description: Joi.string().min(1).max(1500).required(), + note: Joi.number().integer().greater(-1).less(6).required(), + type: Joi.string() + .required() + .custom((value, helpers) => { + const allowedValues = [ + "track", + "album", + "artist", + "single", + "compilation", + ]; + return allowedValues.includes(value) + ? value + : helpers.error("any.invalid"); + }), +}); const deleteReview = Joi.object().keys({ - idReview: Joi - .string() - .min(1) - .max(50) - .required() -}) + idReview: Joi.string().min(1).max(50).required(), +}); const getReviewParams = Joi.object().keys({ - id: Joi - .string() - .min(1) - .max(50) - .required() -}) + id: Joi.string().min(1).max(50).required(), +}); const getReviewQuery = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean() -}) + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), +}); const getReviews = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean() -}) + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), + friendsOnly: Joi.boolean(), +}); const likeReviewParams = Joi.object().keys({ - id: Joi.number().required() -}) + id: Joi.number().required(), +}); const likeReviewQuery = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), -}) + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), +}); const userReviewParams = Joi.object().keys({ - id: Joi.string().max(50).required() -}) + id: Joi.string().max(50).required(), +}); const userReviewQuery = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean() -}) + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), +}); const oeuvreReviewParams = Joi.object().keys({ - id: Joi.string().max(50).required() -}) + id: Joi.string().max(50).required(), +}); const oeuvreReviewQuery = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean() -}) - + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), + friendsOnly: Joi.boolean(), +}); const artistReviewParams = Joi.object().keys({ - id: Joi.string().max(50).required() -}) + id: Joi.string().max(50).required(), +}); const artistReviewQuery = Joi.object().keys({ - page: Joi.number().integer().min(1).required(), - pageSize: Joi.number().integer().min(1).required(), - orderByLike: Joi.boolean() -}) + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), + friendsOnly: Joi.boolean(), +}); const putCommentParams = Joi.object().keys({ - id: Joi.string().max(50).required() -}) + id: Joi.string().max(50).required(), +}); const putCommentPayload = Joi.object().keys({ - description: Joi.string().max(1500).required() -}) + description: Joi.string().max(1500).required(), +}); module.exports = { - putReview, - deleteReview, - getReviewParams, - getReviews, - likeReviewParams, - likeReviewQuery, - userReviewParams, - userReviewQuery, - putCommentParams, - oeuvreReviewQuery, - putCommentPayload, - getReviewQuery, - oeuvreReviewParams, - artistReviewParams, - artistReviewQuery, -} \ No newline at end of file + putReview, + deleteReview, + getReviewParams, + getReviews, + likeReviewParams, + likeReviewQuery, + userReviewParams, + userReviewQuery, + putCommentParams, + oeuvreReviewQuery, + putCommentPayload, + getReviewQuery, + oeuvreReviewParams, + artistReviewParams, + artistReviewQuery, +}; diff --git a/lib/domain/model/ArtistPage.js b/lib/domain/model/ArtistPage.js index 5756d84..f8c0445 100644 --- a/lib/domain/model/ArtistPage.js +++ b/lib/domain/model/ArtistPage.js @@ -1,10 +1,19 @@ module.exports = class { - constructor(artist,albums,friends_followers,reviewsByLike,reviewsByTime,doesUserFollow){ - this.artist = artist - this.albums = albums - this.friends_followers = friends_followers - this.reviewsByLike = reviewsByLike - this.reviewsByTime = reviewsByTime - this.doesUserFollow = doesUserFollow - } -} \ No newline at end of file + constructor( + artist, + albums, + friends_followers, + reviewsByLike, + reviewsByTime, + reviewsByFriends, + doesUserFollow + ) { + this.artist = artist; + this.albums = albums; + this.friends_followers = friends_followers; + this.reviewsByLike = reviewsByLike; + this.reviewsByTime = reviewsByTime; + this.reviewsByFriends = reviewsByFriends; + this.doesUserFollow = doesUserFollow; + } +}; diff --git a/lib/domain/model/ReviewPublic.js b/lib/domain/model/ReviewPublic.js index 057676a..9fda81d 100644 --- a/lib/domain/model/ReviewPublic.js +++ b/lib/domain/model/ReviewPublic.js @@ -1,15 +1,16 @@ -module.exports = class { - constructor(rawReview,oeuvre,utilisateur,doesUserLike,comments) { - this.id_review = rawReview.id_review - this.description = rawReview.description - this.countlike = rawReview.countlike - this.countComment = rawReview.countComment - this.doesUserLike = doesUserLike - this.note = rawReview.note - this.createdAt = rawReview.createdAt - this.oeuvre = oeuvre - this.utilisateur = utilisateur - this.comments = comments - this.type = rawReview.type - } -} \ No newline at end of file +module.exports = class { + constructor(rawReview, oeuvre, utilisateur, doesUserLike, comments) { + this.id_review = rawReview.id_review; + this.description = rawReview.description; + this.countlike = rawReview.countlike; + this.countComment = rawReview.countComment; + this.doesUserLike = doesUserLike; + this.note = rawReview.note; + this.createdAt = rawReview.createdAt; + this.oeuvre = oeuvre; + this.utilisateur = utilisateur; + this.comments = comments; + this.made_by_friend = rawReview.made_by_friend; + this.type = rawReview.type; + } +}; diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index d6d365a..989774a 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -1,141 +1,121 @@ -'use strict'; -const { Sequelize } = require('sequelize'); -const environment = require('../../config/environment'); +"use strict"; +const { Sequelize } = require("sequelize"); +const environment = require("../../config/environment"); const sequelize = new Sequelize(environment.database.url); -const UserModel = require('./models/Utilisateur')(sequelize) -const RoleModel = require('./models/Role')(sequelize) -const AmisModel = require('./models/Amis')(sequelize) -const ArtisteModel = require('./models/Artiste')(sequelize) -const ReviewModel = require('./models/Review')(sequelize) -const TypeReviewModel = require('./models/TypeReview')(sequelize) -const CommentaireModel = require('./models/Commentaire')(sequelize) -require('./models/LikeOeuvre')(sequelize) -require('./models/OeuvreFavorite')(sequelize) - - +const UserModel = require("./models/Utilisateur")(sequelize); +const RoleModel = require("./models/Role")(sequelize); +const AmisModel = require("./models/Amis")(sequelize); +const ArtisteModel = require("./models/Artiste")(sequelize); +const ReviewModel = require("./models/Review")(sequelize); +const TypeReviewModel = require("./models/TypeReview")(sequelize); +const CommentaireModel = require("./models/Commentaire")(sequelize); +require("./models/LikeOeuvre")(sequelize); +require("./models/OeuvreFavorite")(sequelize); //un utilisateur a un role -UserModel.belongsTo(RoleModel, - {foreignKey: 'id_role'}) +UserModel.belongsTo(RoleModel, { foreignKey: "id_role" }); //un utilisateur a un etat - -UserModel.belongsToMany(UserModel, - { - as: 'ami', - foreignKey: { - name:'id_utilisateur', - primaryKey: true, - }, - through: { - model: AmisModel, - primaryKey: true, - } - } -) - -UserModel.belongsToMany(CommentaireModel, - { - as: 'user_like_comment', - foreignKey: 'id_utilisateur', - through : 'like_commentaire', - onDelete: "cascade" - } -) - -CommentaireModel.belongsToMany(UserModel, - { - as: 'comment_like', - foreignKey: 'id_com', - through : 'like_commentaire' - } -) - -UserModel.belongsToMany(ArtisteModel, - { - as: 'utilisateur_m_n', - foreignKey: 'id_utilisateur', - through : 'follow' - } -) -ArtisteModel.belongsToMany(UserModel, - { - as: 'artiste', - foreignKey: 'id_artiste', - through : 'follow' - } -) -UserModel.belongsToMany(ReviewModel,{ - as: 'user_like_review', - foreignKey: 'id_utilisateur', - through : 'like_review', - onDelete: "cascade" -}) -ReviewModel.belongsToMany(UserModel,{ - as: 'review_like', - foreignKey: 'id_review', - through : 'like_review' -}) +UserModel.belongsToMany(UserModel, { + as: "ami", + foreignKey: { + name: "id_utilisateur", + primaryKey: true, + }, + through: { + model: AmisModel, + primaryKey: true, + }, +}); + +UserModel.belongsToMany(CommentaireModel, { + as: "user_like_comment", + foreignKey: "id_utilisateur", + through: "like_commentaire", +}); + +CommentaireModel.belongsToMany(UserModel, { + as: "comment_like", + foreignKey: "id_com", + through: "like_commentaire", +}); + +UserModel.belongsToMany(ArtisteModel, { + as: "utilisateur_m_n", + foreignKey: "id_utilisateur", + through: "follow", +}); +ArtisteModel.belongsToMany(UserModel, { + as: "artiste", + foreignKey: "id_artiste", + through: "follow", +}); +UserModel.belongsToMany(ReviewModel, { + as: "user_like_review", + foreignKey: "id_utilisateur", + through: "like_review", +}); +ReviewModel.belongsToMany(UserModel, { + as: "review_like", + foreignKey: "id_review", + through: "like_review", +}); ReviewModel.belongsTo(UserModel, { - as:'utilisateur', - foreignKey:'id_utilisateur' -}) - + as: "utilisateur", + foreignKey: "id_utilisateur", +}); ReviewModel.belongsTo(TypeReviewModel, { - as:'type_review', - foreignKey:'id_type_review' -}) - + as: "type_review", + foreignKey: "id_type_review", +}); ReviewModel.hasMany(CommentaireModel, { - as:'comment_review', - foreignKey:'id_review', - onDelete: "cascade" -}) -CommentaireModel.belongsTo(ReviewModel,{ - as:'review_comment', - foreignKey:'id_review' -}) - -CommentaireModel.belongsTo(UserModel,{ - as:'user_review', - foreignKey:'id_utilisateur' -}) -CommentaireModel.hasMany(CommentaireModel,{ - as: 'reponse', - foreignKey: 'id_reponse', - onDelete: "cascade" - -}) - + as: "comment_review", + foreignKey: "id_review", + onDelete: "cascade", +}); +CommentaireModel.belongsTo(ReviewModel, { + as: "review_comment", + foreignKey: "id_review", +}); + +CommentaireModel.belongsTo(UserModel, { + as: "user_review", + foreignKey: "id_utilisateur", +}); +CommentaireModel.hasMany(CommentaireModel, { + as: "reponse", + foreignKey: "id_reponse", + onDelete: "cascade", +}); RoleModel.sync() - .then(()=>{ + .then(() => { return RoleModel.bulkCreate([ - {id_role: 1, libelle : 'administrateur'}, - {id_role: 2, libelle : 'utlisateur'}, - ]) - }) - .then(() => console.log('Creation des roles réussi')) - .catch((error) => { - console.error('Erreur dans la création des roles : '+error) - }) + { id_role: 1, libelle: "administrateur" }, + { id_role: 2, libelle: "utlisateur" }, + ]); + }) + .then(() => console.log("Creation des roles réussi")) + .catch((error) => { + console.error("Erreur dans la création des roles : " + error); + }); TypeReviewModel.sync() - .then(()=>{ - return TypeReviewModel.bulkCreate([ - {id_type_review: 1, libelle : 'track'}, - {id_type_review: 2, libelle : 'album'}, - {id_type_review: 3, libelle : 'artist'}, - {id_type_review: 4, libelle : 'single'}, - {id_type_review: 4, libelle : 'compilation'}, - ]) - }) - .then(() => console.log('Creation des types de reviews réussi')) - .catch((error) => { - console.error('Erreur dans la création des types de reviews : '+error) - }) -module.exports = sequelize; \ No newline at end of file + .then(() => { + return TypeReviewModel.bulkCreate([ + { id_type_review: 1, libelle: "track" }, + { id_type_review: 2, libelle: "album" }, + { id_type_review: 3, libelle: "artist" }, + { id_type_review: 4, libelle: "single" }, + { id_type_review: 4, libelle: "compilation" }, + ]); + }) + .then(() => console.log("Creation des types de reviews réussi")) + .catch((error) => { + console.error("Erreur dans la création des types de reviews : " + error); + }); +module.exports = sequelize; diff --git a/lib/infrastructure/repositories/CommentRepository.js b/lib/infrastructure/repositories/CommentRepository.js index 18a7a95..cf72c22 100644 --- a/lib/infrastructure/repositories/CommentRepository.js +++ b/lib/infrastructure/repositories/CommentRepository.js @@ -45,7 +45,7 @@ module.exports = class extends CommentRepository { whereClause.id_utilisateur = id_utilisateur ? { [Op.in]: sequelize.literal( - `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))` + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` ), } : { @@ -66,7 +66,7 @@ module.exports = class extends CommentRepository { ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted = false)" ), "countComment", ], @@ -118,7 +118,7 @@ module.exports = class extends CommentRepository { ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted = false)" ), "countComment", ], @@ -192,7 +192,7 @@ module.exports = class extends CommentRepository { whereClause.id_utilisateur = id_utilisateur ? { [Op.in]: sequelize.literal( - `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))` + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` ), } : { @@ -214,7 +214,7 @@ module.exports = class extends CommentRepository { ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted = false)" ), "countComment", ], diff --git a/lib/infrastructure/repositories/ReviewRepository.js b/lib/infrastructure/repositories/ReviewRepository.js index dc6de58..d15caa7 100644 --- a/lib/infrastructure/repositories/ReviewRepository.js +++ b/lib/infrastructure/repositories/ReviewRepository.js @@ -1,308 +1,354 @@ -'use strict'; +"use strict"; -const sequelize = require('../orm/sequelize/sequelize'); -const ReviewRepository = require('./interfaces/ReviewRepositoryAbstract'); -const {Op} = require('sequelize'); +const sequelize = require("../orm/sequelize/sequelize"); +const ReviewRepository = require("./interfaces/ReviewRepositoryAbstract"); +const { Op } = require("sequelize"); const User = require("../../domain/model/User"); const Review = require("../../domain/model/Review"); -module.exports = class extends ReviewRepository { - - constructor() { - super(); - this.db = sequelize; - this.review = this.db.model('review'); - this.TypeReview = this.db.model('type_review'); - this.user = this.db.model('utilisateur'); - this.likeReviewModel = this.db.model('like_review') - } - createReview(seqReview) { - if(!seqReview) return null - const type = seqReview.type_review.dataValues.libelle - return new Review(seqReview,new User(seqReview.utilisateur),type) - } - async persist(ReviewEntity) { - let seqReview = await this.review.create(ReviewEntity, { - include: [{ - model: this.user, - as: "utilisateur", - attributes: ['alias'] - }] - }); - return this.getById(seqReview.id_review) +const { get } = require("underscore"); +const getPreWhere = (fetchPrivate, id, friendsOnly) => { + let whereClause = { + deleted: false, + }; + if (!fetchPrivate) { + if (friendsOnly && id) { + whereClause.id_utilisateur = { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` + ), + }; + } else { + whereClause.id_utilisateur = id + ? { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` + ), + } + : { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false` + ), + }; } - async getByUserAndId(id_oeuvre,id_utilisateur) { - return await this.review.findOne({ - where: { - id_oeuvre, - id_utilisateur, - deleted: false - } - }) - } - async getTypeReviewID(label){ - const seqTypeReview = await this.TypeReview.findOne({ - where: { - libelle: label - } - }); - return seqTypeReview?.id_type_review + } + return whereClause; +}; +module.exports = class extends ReviewRepository { + constructor() { + super(); + this.db = sequelize; + this.review = this.db.model("review"); + this.TypeReview = this.db.model("type_review"); + this.user = this.db.model("utilisateur"); + this.likeReviewModel = this.db.model("like_review"); + } + createReview(seqReview) { + if (!seqReview) return null; + const type = seqReview.type_review.dataValues.libelle; + return new Review(seqReview, new User(seqReview.utilisateur), type); + } + async persist(ReviewEntity) { + let seqReview = await this.review.create(ReviewEntity, { + include: [ + { + model: this.user, + as: "utilisateur", + attributes: ["alias"], + }, + ], + }); + return this.getById(seqReview.id_review); + } + async getByUserAndId(id_oeuvre, id_utilisateur) { + return await this.review.findOne({ + where: { + id_oeuvre, + id_utilisateur, + deleted: false, + }, + }); + } + async getTypeReviewID(label) { + const seqTypeReview = await this.TypeReview.findOne({ + where: { + libelle: label, + }, + }); + return seqTypeReview?.id_type_review; + } - } + async delete(id_review, id_utilisateur) { + const seqReview = await this.review.findOne({ + where: { + id_review, + id_utilisateur, + }, + }); + if (!seqReview) return false; + seqReview.deleted = true; + seqReview.save(); + return true; + } + async getById(id_review, deleted = false) { + const seqReview = await this.review.findOne({ + where: { + id_review: id_review, + deleted, + }, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(DISTINCT like_review.id_utilisateur ) FROM like_review WHERE like_review.id_review = review.id_review)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review and commentaire.deleted = false and commentaire.id_reponse = null)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review", + }, + ], + }); + return this.createReview(seqReview); + } - async delete(id_review,id_utilisateur) { - const seqReview = await this.review.findOne({ - where: { - id_review, - id_utilisateur - } - }); - if(!seqReview) return false - seqReview.deleted = true - seqReview.save() - return true - } - async getById(id_review,deleted = false){ - const seqReview = await this.review.findOne({ - where: { - id_review: id_review, - deleted - }, - attributes: { - include: [ - [ - sequelize.literal('(SELECT COUNT(DISTINCT like_review.id_utilisateur ) FROM like_review WHERE like_review.id_review = review.id_review)'), - 'countLike' - ], - [ - sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), - 'countComment' - ], - ] - }, - include: [ - { - model: this.user, - as: "utilisateur", - }, - { - model: this.TypeReview, - attributes: ["libelle"], - as: "type_review" - } - ] + async getReviews(page, pageSize, orderByLike, fetchPrivate, friendsOnly, id) { + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + let whereClause = getPreWhere(fetchPrivate, id, friendsOnly); + const reviews = await this.review.findAll({ + limit: pageSize, + offset: offset, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review and commentaire.deleted = false and commentaire.id_reponse = null)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review", + }, + ], + order, + where: whereClause, + }); + return reviews.map((item) => this.createReview(item)); + } - }); - return this.createReview(seqReview) + async getOeuvreReviews( + page, + pageSize, + orderByLike, + fetchPrivate, + friendsOnly, + ids_oeuvre, + id + ) { + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const offset = (page - 1) * pageSize; + let whereClause = getPreWhere(fetchPrivate, id, friendsOnly); + if (Array.isArray(ids_oeuvre) && ids_oeuvre.length > 0) { + whereClause.id_oeuvre = { [Op.in]: ids_oeuvre }; + } else { + whereClause.id_oeuvre = ids_oeuvre; } - async getReviews(page,pageSize,orderByLike, fetchPrivate, id) { - const orderColumn = orderByLike ? "countLike" : "createdAt"; - const order = [[sequelize.literal(orderColumn), "DESC"]]; - const offset = (page - 1) * pageSize; - let whereClause = { - deleted: false - } - if(!fetchPrivate) { - whereClause.id_utilisateur = id - ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} - : {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false`)} + const reviews = await this.review.findAll({ + limit: pageSize, + offset: offset, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review and commentaire.deleted = false and commentaire.id_reponse = null)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review", + }, + ], + order, + where: whereClause, + }); + return reviews.map((item) => this.createReview(item)); + } + async doesUserLikes(id_utilisateur, id_review) { + const count = await this.likeReviewModel.count({ + where: { + id_utilisateur, + id_review, + }, + }); + return count > 0; + } + async likeReview(id_utilisateur, id_review) { + const seqLike = await this.likeReviewModel.create({ + id_review, + id_utilisateur, + }); + await seqLike.save(); + } + async unlikeReview(id_utilisateur, id_review) { + await this.likeReviewModel.destroy({ + where: { + id_utilisateur, + id_review, + }, + }); + } + async getLikes(id_utilisateur, id_review, page, pageSize) { + const offset = (page - 1) * pageSize; + const whereClause = {}; + whereClause["$review_like->like_review.id_review$"] = id_review; + whereClause.id_utilisateur = id_utilisateur + ? { + [Op.in]: sequelize.literal( + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))` + ), } + : { + [Op.in]: sequelize.literal( + "(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)" + ), + }; + const reviews = await this.review.findAll({ + offset: offset, + include: [ + { + model: this.user, + as: "review_like", + where: whereClause, + }, + ], + }); + const limit = + reviews[0]?.review_like.length > pageSize + ? pageSize + : reviews[0]?.review_like.length; + return reviews[0]?.review_like + ? reviews[0]?.review_like.slice(0, limit) + : []; + } - const reviews = await this.review.findAll({ - limit: pageSize, - offset: offset, - attributes: { - include: [ - [ - sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), - 'countLike' - ], - [ - sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), - 'countComment' - ], - ] - }, - include: [ - { - model: this.user, - as: "utilisateur", - }, - { - model: this.TypeReview, - attributes: ["libelle"], - as: "type_review" - } - ], - order, - where: whereClause, - }) - return reviews.map(item => this.createReview(item)) - } + async getReviewByUserId(id_utilisateur, page, pageSize, orderByLike) { + const offset = (page - 1) * pageSize; + const orderColumn = orderByLike ? "countLike" : "createdAt"; + const order = [[sequelize.literal(orderColumn), "DESC"]]; + const reviews = await this.review.findAll({ + offset: offset, + limit: pageSize, + attributes: { + include: [ + [ + sequelize.literal( + "(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)" + ), + "countLike", + ], + [ + sequelize.literal( + "(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review and commentaire.deleted = false and commentaire.id_reponse = null)" + ), + "countComment", + ], + ], + }, + include: [ + { + model: this.user, + as: "utilisateur", + }, + { + model: this.TypeReview, + attributes: ["libelle"], + as: "type_review", + }, + ], + where: { + id_utilisateur, + deleted: false, + }, + order, + }); + return reviews.map((item) => this.createReview(item)); + } - async getOeuvreReviews(page,pageSize,orderByLike, fetchPrivate,ids_oeuvre, id) { - const orderColumn = orderByLike ? "countLike" : "createdAt"; - const order = [[sequelize.literal(orderColumn), "DESC"]]; - const offset = (page - 1) * pageSize; - let whereClause - if (Array.isArray(ids_oeuvre) && ids_oeuvre.length > 0) { - whereClause = { id_oeuvre: { [Op.in]: ids_oeuvre }, deleted: false }; - } else { - whereClause = { id_oeuvre: ids_oeuvre, deleted: false}; - } - if(!fetchPrivate) { - whereClause.id_utilisateur = id - ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id}))`)} - : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} - } - const reviews = await this.review.findAll({ - limit: pageSize, - offset: offset, - attributes: { - include: [ - [ - sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), - 'countLike' - ], - [ - sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review AND commentaire.deleted = 0)'), - 'countComment' - ], - ] - }, - include: [ - { - model: this.user, - as: "utilisateur", - }, - { - model: this.TypeReview, - attributes: ["libelle"], - as: "type_review" - } - ], - order, - where: whereClause, - }) - return reviews.map(item => this.createReview(item)) - } - async doesUserLikes(id_utilisateur,id_review) { - const count = await this.likeReviewModel.count({ - where: { - id_utilisateur, - id_review - }, - }); - return count > 0 - } - async likeReview(id_utilisateur,id_review) { - const seqLike = await this.likeReviewModel.create({ - id_review, - id_utilisateur - }); - await seqLike.save(); - } - async unlikeReview(id_utilisateur,id_review) { - await this.likeReviewModel.destroy({ - where: { - id_utilisateur, - id_review - } - }) - } - async getLikes(id_utilisateur,id_review,page,pageSize) { - const offset = (page - 1) * pageSize; - const whereClause = {} - whereClause['$review_like->like_review.id_review$'] = id_review - whereClause.id_utilisateur = id_utilisateur - ? {[Op.in]: sequelize.literal(`(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur}))`)} - : {[Op.in]: sequelize.literal('(SELECT id_utilisateur FROM utilisateur WHERE is_private = false)')} - const reviews = await this.review.findAll({ - offset: offset, - include: [ - { - model: this.user, - as: "review_like", - where: whereClause, - }, - ], + async getOeuvreRating(id_oeuvre) { + const seqReview = await this.review.findOne({ + where: { + id_oeuvre, + deleted: false, + }, + attributes: [[sequelize.fn("AVG", sequelize.col("note")), "rating"]], + }); + return seqReview.dataValues.rating; + } - }) - const limit = reviews[0]?.review_like.length > pageSize ? pageSize : reviews[0]?.review_like.length - return reviews[0]?.review_like ? reviews[0]?.review_like.slice(0,limit) : [] - } - - async getReviewByUserId(id_utilisateur,page,pageSize,orderByLike) { - const offset = (page - 1) * pageSize; - const orderColumn = orderByLike ? "countLike" : "createdAt"; - const order = [[sequelize.literal(orderColumn), "DESC"]]; - - const reviews = await this.review.findAll({ - offset: offset, - limit: pageSize, - attributes: { - include: [ - [ - sequelize.literal('(SELECT COUNT(*) FROM like_review WHERE like_review.id_review = review.id_review)'), - 'countLike' - ], - [ - sequelize.literal('(SELECT COUNT(*) FROM commentaire WHERE commentaire.id_review = review.id_review)'), - 'countComment' - ], - ] - }, - include: [ - { - model: this.user, - as: "utilisateur", - }, - { - model: this.TypeReview, - attributes: ["libelle"], - as: "type_review" - } - ], - where: { - id_utilisateur, - deleted: false - }, - order - }) - return reviews.map(item => this.createReview(item)) - } + async doesUserLike(id_utilisateur, id_review) { + return !!(await this.likeReviewModel.findOne({ + where: { + id_utilisateur, + id_review, + }, + })); + } - async getOeuvreRating(id_oeuvre) { - const seqReview = await this.review.findOne({ - where: { - id_oeuvre, - deleted: false - }, - attributes: [ - [sequelize.fn('AVG', sequelize.col('note')), 'rating'] - ], - }); - return seqReview.dataValues.rating - } - - async doesUserLike(id_utilisateur,id_review) { - return !!(await this.likeReviewModel.findOne({ - where: { - id_utilisateur, - id_review - }, - })) - - } - - async getReviewCount(id_oeuvre) { - return await this.review.count({ - where: { - id_oeuvre, - deleted: false - }, - }) - } + async getReviewCount(id_oeuvre) { + return await this.review.count({ + where: { + id_oeuvre, + deleted: false, + }, + }); + } }; - diff --git a/lib/interfaces/controllers/ReviewController.js b/lib/interfaces/controllers/ReviewController.js index 991021b..9ef0644 100644 --- a/lib/interfaces/controllers/ReviewController.js +++ b/lib/interfaces/controllers/ReviewController.js @@ -12,155 +12,231 @@ const getCountOeuvreReviews = require("../../application/use_cases/review/getCou const handleError = require("./utils/handleError"); module.exports = { + async putReview(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { idOeuvre, note, description, type } = request.payload; - async putReview(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const [, token ] = authorizationHeader.split(' '); - const {idOeuvre, note, description,type} = request.payload + return handler + .response( + await putReview( + idOeuvre, + token, + description, + note, + type, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async deleteReview(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { idReview } = request.payload; + await deleteReview(idReview, token, serviceLocator); + return handler.response("").code(200); + } catch (error) { + return handleError(error); + } + }, + async getReview(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const { id } = request.params; + const { page, pageSize, orderByLike } = request.query; + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + return handler + .response( + await getReview( + id, + token, + page, + pageSize, + orderByLike, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async getReviews(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const { page, pageSize, orderByLike, friendsOnly } = request.query; + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + return handler + .response( + await getReviews( + page, + pageSize, + orderByLike, + friendsOnly, + token, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async likeReview(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const { id } = request.params; + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + await likeReview(id, token, serviceLocator); + return handler.response("").code(200); + } catch (error) { + return handleError(error); + } + }, + async getLikes(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { page, pageSize } = request.query; + return handler + .response( + await getReviewLikes(id, token, page, pageSize, serviceLocator) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async getUserReviews(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { page, pageSize, orderByLike } = request.query; + return handler + .response( + await getUserReviews( + id, + token, + page, + pageSize, + orderByLike, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async getOeuvreReviews(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { page, pageSize, orderByLike } = request.query; + const response = { + data: await getOeuvreReviews( + id, + token, + page, + pageSize, + orderByLike, + serviceLocator + ), + count: await getCountOeuvreReviews(id, token, serviceLocator), + }; + return handler.response(response).code(200); + } catch (error) { + return handleError(error); + } + }, + async getArtistReviews(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { page, pageSize, orderByLike, friendsOnly } = request.query; + return handler + .response( + await getArtistReviews( + id, + token, + page, + pageSize, + orderByLike, + friendsOnly, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, - return handler.response(await putReview(idOeuvre, token, description, note,type, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async deleteReview(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token ] = authorizationHeader.split(' '); - const {idReview} = request.payload - await deleteReview(idReview, token, serviceLocator) - return handler.response('').code(200) - }catch(error){ - return handleError(error) - } - }, - async getReview(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const {id} = request.params - const {page, pageSize,orderByLike} = request.query - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - return handler.response(await getReview(id,token,page, pageSize,orderByLike, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async getReviews(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const {page, pageSize, orderByLike} = request.query - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - return handler.response(await getReviews(page,pageSize,orderByLike,token, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async likeReview(request, handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const {id} = request.params - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - await likeReview(id,token,serviceLocator) - return handler.response('').code(200) - }catch(error){ - return handleError(error) - } - }, - async getLikes(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {page, pageSize} = request.query - return handler.response(await getReviewLikes(id, token,page,pageSize, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async getUserReviews(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {page, pageSize,orderByLike} = request.query - return handler.response(await getUserReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async getOeuvreReviews(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {page, pageSize,orderByLike} = request.query - const response = { - data: await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator), - count: await getCountOeuvreReviews(id,token, serviceLocator) - }; - return handler.response(response).code(200) - }catch(error){ - return handleError(error) - } - }, - async getArtistReviews(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {page, pageSize,orderByLike} = request.query - return handler.response(await getArtistReviews(id, token,page,pageSize,orderByLike, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - - async putComment(request,handler){ - try{ - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {description} = request.payload - return handler.response(await putComment(token, id,description, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, - async getOeuvreReviews(request,handler){ - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; // a tous les repo - const authorizationHeader = request.headers.authorization; - const token = authorizationHeader?.split(' ')[1]; - const {id} = request.params - const {page, pageSize,orderByLike} = request.query - const response = { - data: await getOeuvreReviews(id, token,page,pageSize,orderByLike, serviceLocator), - count: await getCountOeuvreReviews(id,token, serviceLocator) - }; - return handler.response(response).code(200) - }catch(error){ - return handleError(error) - } - }, -} \ No newline at end of file + async putComment(request, handler) { + try { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { description } = request.payload; + return handler + .response(await putComment(token, id, description, serviceLocator)) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async getOeuvreReviews(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; // a tous les repo + const authorizationHeader = request.headers.authorization; + const token = authorizationHeader?.split(" ")[1]; + const { id } = request.params; + const { page, pageSize, orderByLike, friendsOnly } = request.query; + const response = { + data: await getOeuvreReviews( + id, + token, + page, + pageSize, + orderByLike, + friendsOnly, + serviceLocator + ), + count: await getCountOeuvreReviews(id, token, serviceLocator), + }; + return handler.response(response).code(200); + } catch (error) { + return handleError(error); + } + }, +}; diff --git a/lib/interfaces/routes/review.js b/lib/interfaces/routes/review.js index 8d71cbb..aa0341a 100644 --- a/lib/interfaces/routes/review.js +++ b/lib/interfaces/routes/review.js @@ -1,296 +1,296 @@ const ReviewController = require("../controllers/ReviewController"); const { - putReview, - deleteReview, - getReviews, - getReviewParams, - getReviewQuery, - likeReviewParams, - likeReviewQuery, - userReviewParams, - putCommentParams, - putCommentPayload, - userReviewQuery, - oeuvreReviewParams, - oeuvreReviewQuery, - artistReviewParams, - artistReviewQuery, + putReview, + deleteReview, + getReviews, + getReviewParams, + getReviewQuery, + likeReviewParams, + likeReviewQuery, + userReviewParams, + putCommentParams, + putCommentPayload, + userReviewQuery, + oeuvreReviewParams, + oeuvreReviewQuery, + artistReviewParams, + artistReviewQuery, } = require("../../domain/entity/ReviewEntity"); module.exports = { - name: 'review', - version: '1.0.0', - register: async (server) => { - server.route([ - { - method: 'PUT', - path: '/review', - handler: ReviewController.putReview, - options: { - auth: 'jwt', - description: 'create a review', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description : 'Success'}, - 204: {description : 'No content'}, - 401: {description : 'Unauthorized'}, - 403: {description : 'forbidden'}, - 404: {description : 'Ressource not found'}, - 500: {description : 'Internal server error'}, - 502: {description : 'bad gateway'}, - 503: {description : 'Service unavailable'}, - } - } - }, - validate: { - payload: putReview - } - }, + name: "review", + version: "1.0.0", + register: async (server) => { + server.route([ + { + method: "PUT", + path: "/review", + handler: ReviewController.putReview, + options: { + auth: "jwt", + description: "create a review", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'DELETE', - path: '/review', - handler: ReviewController.deleteReview, - options: { - auth: 'jwt', - description: 'delete a review', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description : 'Success'}, - 204: {description : 'No content'}, - 401: {description : 'Unauthorized'}, - 403: {description : 'forbidden'}, - 404: {description : 'Ressource not found'}, - 500: {description : 'Internal server error'}, - 502: {description : 'bad gateway'}, - 503: {description : 'Service unavailable'}, - } - } - }, - validate: { - payload: deleteReview - } - }, + }, + validate: { + payload: putReview, + }, + }, + }, + { + method: "DELETE", + path: "/review", + handler: ReviewController.deleteReview, + options: { + auth: "jwt", + description: "delete a review", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/review/{id}', - handler: ReviewController.getReview, - options: { - description: 'get a review', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: getReviewParams, - query: getReviewQuery - } - } + }, + validate: { + payload: deleteReview, + }, + }, + }, + { + method: "GET", + path: "/review/{id}", + handler: ReviewController.getReview, + options: { + description: "get a review", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/reviews', - handler: ReviewController.getReviews, - options: { - description: 'get review list', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - query: getReviews - } - } + }, + validate: { + params: getReviewParams, + query: getReviewQuery, + }, + }, + }, + { + method: "GET", + path: "/reviews", + handler: ReviewController.getReviews, + options: { + description: "get review list", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'POST', - path: '/review/{id}/like', - handler: ReviewController.likeReview, - options: { - auth: "jwt", - description: 'like a review', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: likeReviewParams - } - } + }, + validate: { + query: getReviews, + }, + }, + }, + { + method: "POST", + path: "/review/{id}/like", + handler: ReviewController.likeReview, + options: { + auth: "jwt", + description: "like a review", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/review/{id}/likes', - handler: ReviewController.getLikes, - options: { - description: 'get review\' likes', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: likeReviewParams, - query: likeReviewQuery - } - } + }, + validate: { + params: likeReviewParams, + }, + }, + }, + { + method: "GET", + path: "/review/{id}/likes", + handler: ReviewController.getLikes, + options: { + description: "get review' likes", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/reviews/user/{id}', - handler: ReviewController.getUserReviews, - options: { - description: 'get review\' likes', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: userReviewParams, - query: userReviewQuery - } - } + }, + validate: { + params: likeReviewParams, + query: likeReviewQuery, + }, + }, + }, + { + method: "GET", + path: "/reviews/user/{id}", + handler: ReviewController.getUserReviews, + options: { + description: "get review' likes", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/reviews/oeuvre/{id}', - handler: ReviewController.getOeuvreReviews, - options: { - description: 'get oeuvre reviews', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: oeuvreReviewParams, - query: oeuvreReviewQuery - } - } + }, + validate: { + params: userReviewParams, + query: userReviewQuery, + }, + }, + }, + { + method: "GET", + path: "/reviews/oeuvre/{id}", + handler: ReviewController.getOeuvreReviews, + options: { + description: "get oeuvre reviews", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'GET', - path: '/reviews/artist/{id}', - handler: ReviewController.getArtistReviews, - options: { - description: 'get artitst\' reviews', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: artistReviewParams, - query: artistReviewQuery - } - } + }, + validate: { + params: oeuvreReviewParams, + query: oeuvreReviewQuery, + }, + }, + }, + { + method: "GET", + path: "/reviews/artist/{id}", + handler: ReviewController.getArtistReviews, + options: { + description: "get artitst' reviews", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, }, - { - method: 'PUT', - path: '/review/{id}/comment', - handler: ReviewController.putComment, - options: { - auth: 'jwt', - description: 'put a comment', - tags: ['api'], - plugins: { - 'hapi-swagger': { - responses: { - 200: {description: 'Success'}, - 204: {description: 'No content'}, - 401: {description: 'Unauthorized'}, - 403: {description: 'forbidden'}, - 404: {description: 'Ressource not found'}, - 500: {description: 'Internal server error'}, - 502: {description: 'bad gateway'}, - 503: {description: 'Service unavailable'}, - } - } - }, - validate: { - params: putCommentParams, - payload: putCommentPayload - } - } - } - ]); - } -}; \ No newline at end of file + }, + validate: { + params: artistReviewParams, + query: artistReviewQuery, + }, + }, + }, + { + method: "PUT", + path: "/review/{id}/comment", + handler: ReviewController.putComment, + options: { + auth: "jwt", + description: "put a comment", + tags: ["api"], + plugins: { + "hapi-swagger": { + responses: { + 200: { description: "Success" }, + 204: { description: "No content" }, + 401: { description: "Unauthorized" }, + 403: { description: "forbidden" }, + 404: { description: "Ressource not found" }, + 500: { description: "Internal server error" }, + 502: { description: "bad gateway" }, + 503: { description: "Service unavailable" }, + }, + }, + }, + validate: { + params: putCommentParams, + payload: putCommentPayload, + }, + }, + }, + ]); + }, +}; diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js index 8850a76..062f19c 100644 --- a/lib/interfaces/serializers/ReviewSerializer.js +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -1,26 +1,56 @@ const artistSerializer = require("./ArtistSerializer"); const trackSerializer = require("./TrackSerializer"); const albumSerializer = require("./AlbumSerializer"); -const UserPublic = require("../../domain/model/UserPublic") +const UserPublic = require("../../domain/model/UserPublic"); const ReviewPublic = require("../../domain/model/ReviewPublic"); -const serializeReview = (rawReview,rawOeuvre,utilisateur,doesUserLike,comments) => { - switch (rawReview.type){ - case 'artist': - rawOeuvre = artistSerializer(rawOeuvre) - break - case 'track': - rawOeuvre = trackSerializer(rawOeuvre) - break - case 'album': - rawOeuvre = albumSerializer(rawOeuvre) - break - case 'single': - rawOeuvre = albumSerializer(rawOeuvre) - break - default: - rawOeuvre = null - } - return new ReviewPublic(rawReview, rawOeuvre, new UserPublic(utilisateur),doesUserLike,comments) -} +const serializeReview = async ( + rawReview, + id_utilisateur, + comments, + spotifyRepository, + reviewRepository, + friendRepository +) => { + let doesUserLike = false; + if (id_utilisateur) { + doesUserLike = await reviewRepository.doesUserLike( + id_utilisateur, + rawReview.id_review + ); + } + let rawOeuvre = await spotifyRepository.getOeuvre( + rawReview.id_oeuvre, + rawReview.type + ); + if (rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); + rawReview.made_by_friend = await friendRepository.areFriends( + id_utilisateur, + rawReview.utilisateur.id_utilisateur + ); + switch (rawReview.type) { + case "artist": + rawOeuvre = artistSerializer(rawOeuvre); + break; + case "track": + rawOeuvre = trackSerializer(rawOeuvre); + break; + case "album": + rawOeuvre = albumSerializer(rawOeuvre); + break; + case "single": + rawOeuvre = albumSerializer(rawOeuvre); + break; + default: + rawOeuvre = null; + } + return new ReviewPublic( + rawReview, + rawOeuvre, + new UserPublic(rawReview.utilisateur), + doesUserLike, + comments + ); +}; -module.exports = serializeReview \ No newline at end of file +module.exports = serializeReview; From acec6ffcbeab55ce6b9e7cdd1dbb3826fe9c5923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl?= Date: Tue, 30 Apr 2024 14:22:20 +0200 Subject: [PATCH 59/67] corrected putreview --- lib/application/use_cases/review/putReview.js | 4 ++++ lib/domain/entity/ReviewEntity.js | 2 +- lib/infrastructure/orm/sequelize/sequelize.js | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/application/use_cases/review/putReview.js b/lib/application/use_cases/review/putReview.js index 1ae6ba4..19f9ecd 100644 --- a/lib/application/use_cases/review/putReview.js +++ b/lib/application/use_cases/review/putReview.js @@ -12,6 +12,7 @@ module.exports = async ( reviewRepository, spotifyRepository, friendRepository, + oeuvreRepository, } ) => { const id_utilisateur = accessTokenManager.decode(userToken)?.value; @@ -25,6 +26,9 @@ module.exports = async ( const rawOeuvre = await spotifyRepository.getOeuvre(idOeuvre, type); if (rawOeuvre.error) throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); + if (oeuvreRepository.is_oeuvre_relation(idOeuvre)) { + console.log(rawOeuvre); + } const ReviewRaw = { id_oeuvre: idOeuvre, id_utilisateur, diff --git a/lib/domain/entity/ReviewEntity.js b/lib/domain/entity/ReviewEntity.js index 5fcdd70..7373509 100644 --- a/lib/domain/entity/ReviewEntity.js +++ b/lib/domain/entity/ReviewEntity.js @@ -2,7 +2,7 @@ const Joi = require("joi"); const putReview = Joi.object().keys({ idOeuvre: Joi.string().min(1).max(50).required(), description: Joi.string().min(1).max(1500).required(), - note: Joi.number().integer().greater(-1).less(6).required(), + note: Joi.number().greater(-1).less(6).required(), type: Joi.string() .required() .custom((value, helpers) => { diff --git a/lib/infrastructure/orm/sequelize/sequelize.js b/lib/infrastructure/orm/sequelize/sequelize.js index 989774a..8268c61 100644 --- a/lib/infrastructure/orm/sequelize/sequelize.js +++ b/lib/infrastructure/orm/sequelize/sequelize.js @@ -111,7 +111,7 @@ TypeReviewModel.sync() { id_type_review: 2, libelle: "album" }, { id_type_review: 3, libelle: "artist" }, { id_type_review: 4, libelle: "single" }, - { id_type_review: 4, libelle: "compilation" }, + { id_type_review: 5, libelle: "compilation" }, ]); }) .then(() => console.log("Creation des types de reviews réussi")) From ad6ada4bef72ca1f3be4cb88b433617bbde7c450 Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Tue, 30 Apr 2024 14:58:57 +0200 Subject: [PATCH 60/67] =?UTF-8?q?Fix=20de=20la=20requete=20countlike=20sur?= =?UTF-8?q?=20les=20commentaires=20pour=20pas=20prendre=20en=20compte=20le?= =?UTF-8?q?s=20commentaires=20supprim=C3=A9s=20+=20fix=20de=20likeOeuvre?= =?UTF-8?q?=20pour=20appeler=20la=20bonne=20fonction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/application/use_cases/oeuvre/likeOeuvre.js | 2 +- lib/infrastructure/repositories/CommentRepository.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/application/use_cases/oeuvre/likeOeuvre.js b/lib/application/use_cases/oeuvre/likeOeuvre.js index 37e1d05..2e12fed 100644 --- a/lib/application/use_cases/oeuvre/likeOeuvre.js +++ b/lib/application/use_cases/oeuvre/likeOeuvre.js @@ -4,7 +4,7 @@ module.exports = async (userToken, artistId,type, {userRepository,likeOeuvreRepo if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") const oeuvre = spotifyRepository.getOeuvre(artistId,type) if(oeuvre.error) throwStatusCode(oeuvre.error.status,oeuvre.error.message) - if(! await likeOeuvreRepository.doesUserLike(id_utilisateur,artistId)) { + if(! await likeOeuvreRepository.doesUserLikes(id_utilisateur,artistId)) { await likeOeuvreRepository.like(id_utilisateur,artistId) return true } diff --git a/lib/infrastructure/repositories/CommentRepository.js b/lib/infrastructure/repositories/CommentRepository.js index 18a7a95..e0a5905 100644 --- a/lib/infrastructure/repositories/CommentRepository.js +++ b/lib/infrastructure/repositories/CommentRepository.js @@ -60,13 +60,13 @@ module.exports = class extends CommentRepository { include: [ [ sequelize.literal( - "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com and deleted=false)" ), "countLike", ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted=false)" ), "countComment", ], @@ -112,13 +112,13 @@ module.exports = class extends CommentRepository { include: [ [ sequelize.literal( - "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com and deleted=false)" ), "countLike", ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted=false)" ), "countComment", ], @@ -208,13 +208,13 @@ module.exports = class extends CommentRepository { include: [ [ sequelize.literal( - "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com)" + "(SELECT COUNT(*) FROM like_commentaire WHERE like_commentaire.id_com = commentaire.id_com and deleted=false)" ), "countLike", ], [ sequelize.literal( - "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com)" + "(SELECT COUNT(*) FROM commentaire AS comm2 WHERE comm2.id_reponse = commentaire.id_com and deleted=false)" ), "countComment", ], From 467c19905988e91c2376bc975e0a3da72e61ecbe Mon Sep 17 00:00:00 2001 From: yousrah24 Date: Wed, 1 May 2024 13:56:56 +0200 Subject: [PATCH 61/67] Fix de getOeuvre pour recuperer les reviewCount et likecount et rating des tracks d'un album --- lib/application/use_cases/oeuvre/getOeuvre.js | 11 +++++++++++ lib/interfaces/serializers/AlbumTrackSerializer.js | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/application/use_cases/oeuvre/getOeuvre.js b/lib/application/use_cases/oeuvre/getOeuvre.js index fe1c139..2725d97 100644 --- a/lib/application/use_cases/oeuvre/getOeuvre.js +++ b/lib/application/use_cases/oeuvre/getOeuvre.js @@ -56,6 +56,17 @@ module.exports = async ( oeuvre.reviewCount = await reviewRepository.getReviewCount(idOeuvre); oeuvre.rating = await reviewRepository.getOeuvreRating(idOeuvre); + oeuvre?.tracks?.map(async (track) => { + track.likeCount = await likeOeuvreRepository.getLikeCount( track.id); + track.reviewCount = await reviewRepository.getReviewCount( track.id); + track.rating = await reviewRepository.getOeuvreRating( track.id); + track.doesUserLike = await likeOeuvreRepository.doesUserLikes( + idUtilisateur, + track.id + ); + return track; + }); + const reviewsByLike = await reviewRepository.getOeuvreReviews( 1, 3, diff --git a/lib/interfaces/serializers/AlbumTrackSerializer.js b/lib/interfaces/serializers/AlbumTrackSerializer.js index 47dc35d..26b4d5c 100644 --- a/lib/interfaces/serializers/AlbumTrackSerializer.js +++ b/lib/interfaces/serializers/AlbumTrackSerializer.js @@ -6,11 +6,11 @@ const serializeTrack = (trackRaw) => { const track = { id: trackRaw.id, name: trackRaw.name, - album: undefined, - artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, spotify_url : trackRaw?.external_urls.spotify, duration_ms: trackRaw.duration_ms, - popularity: undefined, + rating: trackRaw?.rating, + likeCount: trackRaw?.likeCount, + reviewCount: trackRaw?.reviewCount, }; return new Track(track) }; From 0e6ea576dfb649a4bf15d4a3ca7802d3aea02981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Mon, 6 May 2024 00:20:03 +0200 Subject: [PATCH 62/67] Feature/followers artist (#55) * fix getComment * non prise en compte des commentaires deleted * get artist's followers without tests * FIX get artist's followers * resolution de conflits * fix getComment --- .../use_cases/artist/getArtistFollowers.js | 30 +++++++++++++++---- .../use_cases/comment/getComment.js | 1 + lib/interfaces/routes/artist.js | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/application/use_cases/artist/getArtistFollowers.js b/lib/application/use_cases/artist/getArtistFollowers.js index 1904ee1..a38380a 100644 --- a/lib/application/use_cases/artist/getArtistFollowers.js +++ b/lib/application/use_cases/artist/getArtistFollowers.js @@ -1,5 +1,6 @@ const throwStatusCode = require("../utils/throwStatusCode.js"); const ArtistFollowersPage = require("../../../domain/model/ArtistFollowersPage.js"); +const UserPublic = require("../../../domain/model/UserPublic.js"); module.exports = async (artistId, userToken, {accessTokenManager,userRepository, spotifyRepository, followRepository, friendRepository}) => { const id_utilisateur = accessTokenManager.decode(userToken)?.value @@ -12,10 +13,29 @@ module.exports = async (artistId, userToken, {accessTokenManager,userRepository, const followersFriendsList = await followRepository.getFriendsFollowing(artistId,id_utilisateur) const followersWithoutFriends = await followRepository.getArtistFollowersWithoutFriends(artistId, id_utilisateur) - //const allFollowers = [...followersFriendsList, ...followersWithoutFriends]; - const allFollowers = { - "followersFriendsList": followersFriendsList, - "followersWithoutFriends": followersWithoutFriends - } + + const followersFriendsList2 = await Promise.all(followersFriendsList.map(async (item) => { + const areFriends = await friendRepository.areFriends(id_utilisateur, item.id_utilisateur); + const userPublicFriends = new UserPublic(item) + return { + ...userPublicFriends, + areFriends: areFriends + }; + })); + + const followersWithoutFriendst2 = await Promise.all(followersWithoutFriends.map(async (item) => { + let areFriends = await friendRepository.areFriends(id_utilisateur, item.id_utilisateur); + const userPublicNoFriends = new UserPublic(item) + if(id_utilisateur == item.id_utilisateur) { + areFriends = false + } + return { + ...userPublicNoFriends, + areFriends: areFriends + }; + })); + + const allFollowers = [...followersFriendsList2, ...followersWithoutFriendst2]; + return new ArtistFollowersPage(artist, allFollowers) } \ No newline at end of file diff --git a/lib/application/use_cases/comment/getComment.js b/lib/application/use_cases/comment/getComment.js index 45082b3..3080458 100644 --- a/lib/application/use_cases/comment/getComment.js +++ b/lib/application/use_cases/comment/getComment.js @@ -3,6 +3,7 @@ const PublicComment = require("../../../domain/model/PublicComment"); const UserPublic = require("../../../domain/model/UserPublic"); const throwStatusCode = require("../utils/throwStatusCode"); + module.exports = async ( id_com, page, diff --git a/lib/interfaces/routes/artist.js b/lib/interfaces/routes/artist.js index 754dc7a..7d71258 100644 --- a/lib/interfaces/routes/artist.js +++ b/lib/interfaces/routes/artist.js @@ -36,7 +36,7 @@ module.exports = { }, { method: 'GET', - path: '/artistFollowers/{id}', + path: '/artist/{id}/followers', handler: ArtistController.getFollowers, options: { auth: 'jwt', From 9e5237e1728ec444e9fa7b6c5a46c1b01dacf19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Tue, 7 May 2024 15:41:32 +0200 Subject: [PATCH 63/67] Feature/user page (#56) * save * save * correction * added is private * modify profile * save * .env * test correction * correction on getTracks * save * save * save * correction * added is private * modify profile * save * .env * test correction * correction on getTracks * save --- .../use_cases/friend/followUser.js | 78 +- lib/application/use_cases/spotify/Search.js | 1 + lib/application/use_cases/user/getPage.js | 99 + .../use_cases/user/modifyProfile.js | 36 + lib/application/use_cases/user/oeuvreFav.js | 22 +- lib/domain/entity/UserEntity.js | 169 +- lib/domain/model/OeuvreFav.js | 10 + lib/domain/model/Track.js | 31 +- lib/domain/model/User.js | 4 + lib/domain/model/UserPublic.js | 46 +- lib/infrastructure/config/strategy.js | 2 - .../orm/sequelize/models/OeuvreFavorite.js | 6 +- .../repositories/FriendRepository.js | 136 +- .../repositories/OeuvreFavRepository.js | 19 +- .../repositories/UserRepository.js | 23 +- .../interfaces/FriendRepositoryAbstract.js | 26 +- .../security/JwtAccessTokenManager.js | 4 +- lib/interfaces/controllers/UsersController.js | 414 ++-- lib/interfaces/routes/users.js | 66 +- .../serializers/ArtistSerializer.js | 29 +- .../serializers/OeuvreSerializer.js | 20 + lib/interfaces/serializers/TrackSerializer.js | 35 +- .../fixture/artist/getArtistFixture.js | 367 ++-- test/integration/users.test.js | 1828 +++++++++-------- .../usecase/artist/getArtistFixture.js | 681 +++--- .../usecase/user/getAccessToken.test.js | 190 +- .../user/getUserByConfirmToken.test.js | 88 +- .../usecase/user/getUserByPseudo.test.js | 88 +- .../usecase/user/oeuvreFav.test.js | 458 +++-- 29 files changed, 2741 insertions(+), 2235 deletions(-) create mode 100644 lib/application/use_cases/user/getPage.js create mode 100644 lib/application/use_cases/user/modifyProfile.js create mode 100644 lib/domain/model/OeuvreFav.js create mode 100644 lib/interfaces/serializers/OeuvreSerializer.js diff --git a/lib/application/use_cases/friend/followUser.js b/lib/application/use_cases/friend/followUser.js index dba40cd..bb569ed 100644 --- a/lib/application/use_cases/friend/followUser.js +++ b/lib/application/use_cases/friend/followUser.js @@ -1,38 +1,44 @@ -'use strict'; +"use strict"; -const Friend = require('../../../domain/model/Friend'); +const Friend = require("../../../domain/model/Friend"); const throwStatusCode = require("../utils/throwStatusCode"); -module.exports = async (token, amiIdUtilisateur, {accessTokenManager, friendRepository, userRepository, mailRepository}) => { - const id = accessTokenManager.decode(token)?.value - const user = await userRepository.getByUser(id); - if (!user) { - throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); - } - const ami = await userRepository.getByUser(amiIdUtilisateur); - if (!ami) { - throwStatusCode(400, 'id ami invalide'); - } +module.exports = async ( + token, + amiIdUtilisateur, + { accessTokenManager, friendRepository, userRepository, mailRepository } +) => { + const id = accessTokenManager.decode(token)?.value; + const user = await userRepository.getByUser(id); + if (!user) { + throwStatusCode(400, "Votre token d'authentification n'est pas le bon"); + } + const ami = await userRepository.getByUser(amiIdUtilisateur); + if (!ami) { + throwStatusCode(400, "id ami invalide"); + } + const followInfo = await friendRepository.getFollowInfo(id, amiIdUtilisateur); + console.log(followInfo); + if (followInfo.doesFollows) { + friendRepository.removeFriendById(id, amiIdUtilisateur); + return; + } + const userRaw = { + id_utilisateur: id, + amiIdUtilisateur: amiIdUtilisateur, + en_attente: ami.is_private, + createdAt: undefined, + updatedAt: undefined, + }; - const userRaw = { - id_utilisateur: id, - amiIdUtilisateur: amiIdUtilisateur, - en_attente: ami.is_private, - createdAt: undefined, - updatedAt: undefined - }; - - let friend = new Friend(userRaw); - friend = await friendRepository.persist(friend); - if (!friend) { - throwStatusCode(403, 'Vous avez déjà ajouté cet utilisateur.') - } - if (ami.is_private) { - const mailOptions = { - from: process.env.MAILER_EMAIL, - to: ami.email, - subject: 'Nouvelle demande d\'ami sur Solimbo', - html: ` + let friend = new Friend(userRaw); + friend = await friendRepository.persist(friend); + if (ami.is_private) { + const mailOptions = { + from: process.env.MAILER_EMAIL, + to: ami.email, + subject: "Nouvelle demande d'ami sur Solimbo", + html: ` @@ -86,9 +92,9 @@ module.exports = async (token, amiIdUtilisateur, {accessTokenManager, friendRepo - ` - } - await mailRepository.send(mailOptions); - } - return friend; + `, + }; + await mailRepository.send(mailOptions); + } + return friend; }; diff --git a/lib/application/use_cases/spotify/Search.js b/lib/application/use_cases/spotify/Search.js index c57dc27..ccea3cd 100644 --- a/lib/application/use_cases/spotify/Search.js +++ b/lib/application/use_cases/spotify/Search.js @@ -10,6 +10,7 @@ module.exports = async (query,filter, limit, {spotifyRepository, userRepository} if(filter.includes("user")){ filter = filter.filter(item => item !== "user"); users = await userRepository.getUsersByPseudo(query,MAX_USER) + console.log(users) limitSize -= users.length } diff --git a/lib/application/use_cases/user/getPage.js b/lib/application/use_cases/user/getPage.js new file mode 100644 index 0000000..66f0487 --- /dev/null +++ b/lib/application/use_cases/user/getPage.js @@ -0,0 +1,99 @@ +const throwStatusCode = require("../utils/throwStatusCode"); +const oeuvreSerializer = require("../../../interfaces/serializers/OeuvreSerializer"); +const UserPublic = require("../../../domain/model/UserPublic"); +const reviewSerializer = require("../../../interfaces/serializers/ReviewSerializer"); +module.exports = async ( + id_utilisateur, + page, + pageSize, + orderByLike, + user_token, + { + userRepository, + friendRepository, + oeuvreFavRepository, + reviewRepository, + accessTokenManager, + spotifyRepository, + } +) => { + const current_user = await userRepository.getByUser( + accessTokenManager.decode(user_token)?.value + ); + + if (!current_user) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + const selected_user = await userRepository.getByUser(id_utilisateur); + if (!selected_user) throwStatusCode(404, "l'utilisateur n'existe pas"); + const isCurrent = id_utilisateur == current_user.id_utilisateur; + const doesFollows = await friendRepository.doesFollows( + id_utilisateur, + current_user.id_utilisateur + ); + const relation = await friendRepository.getFollowInfo( + id_utilisateur, + current_user.id_utilisateur + ); + if (selected_user.is_private && !doesFollows) { + return { + user: new UserPublic(selected_user), + forbidden: true, + relation, + }; + } + + const idOeuvres = await oeuvreFavRepository.getOeuvresFav(id_utilisateur); + const reviewsRaw = await reviewRepository.getReviewByUserId( + id_utilisateur, + page, + pageSize, + orderByLike + ); + + const oeuvresPromise = Promise.all( + idOeuvres.map(async (oeuvre) => { + const SpotifyOeuvre = await spotifyRepository.getOeuvre( + oeuvre.id_oeuvre, + oeuvre.type + ); + SpotifyOeuvre.rating = await reviewRepository.getOeuvreRating( + SpotifyOeuvre.id + ); + return oeuvreSerializer(SpotifyOeuvre, oeuvre.type); + }) + ); + + const reviewsPromise = Promise.all( + reviewsRaw.map(async (review) => { + const rawOeuvre = await spotifyRepository.getOeuvre( + review.id_oeuvre, + review.type + ); + if (rawOeuvre.error) + throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); + const doesUserLike = await reviewRepository.doesUserLike( + id_utilisateur, + review.id_review + ); + return reviewSerializer( + review, + rawOeuvre, + review.utilisateur, + doesUserLike + ); + }) + ); + + const [oeuvres, reviews] = await Promise.all([ + oeuvresPromise, + reviewsPromise, + ]); + return { + user: new UserPublic(selected_user), + forbidden: false, + isCurrent, + relation, + oeuvres, + reviews, + }; +}; diff --git a/lib/application/use_cases/user/modifyProfile.js b/lib/application/use_cases/user/modifyProfile.js new file mode 100644 index 0000000..afd6232 --- /dev/null +++ b/lib/application/use_cases/user/modifyProfile.js @@ -0,0 +1,36 @@ +"use strict"; +const isImage = require("../utils/isImage"); +const throwStatusCode = require("../utils/throwStatusCode"); +const UserPublic = require("../../../domain/model/UserPublic"); +module.exports = async ( + file, + pseudo, + bio, + alias, + isPrivate, + token, + { accessTokenManager, userRepository, documentRepository } +) => { + const id = accessTokenManager.decode(token)?.value; + const user = await userRepository.getByUser(id); + if (!user) + throwStatusCode(401, "votre token d'authentification n'est pas le bon"); + if (file) { + if (!isImage(file)) + throwStatusCode(415, "le fichier fourni n'est pas une image"); + const previewPath = user.photo; + if (previewPath) { + await documentRepository.deleteFile(previewPath); + } + const path = await documentRepository.uploadFile("upload", file); + if (!path) throwStatusCode(500, "internal server error"); + user.photo = path; + } + if (pseudo) user.pseudo = pseudo; + if (bio) user.bio = bio; + if (alias) user.alias = alias; + if (isPrivate !== undefined) user.is_private = isPrivate; + + await userRepository.updateUser(user); + return new UserPublic(user); +}; diff --git a/lib/application/use_cases/user/oeuvreFav.js b/lib/application/use_cases/user/oeuvreFav.js index f6b9412..99887e5 100644 --- a/lib/application/use_cases/user/oeuvreFav.js +++ b/lib/application/use_cases/user/oeuvreFav.js @@ -3,30 +3,18 @@ const getAlbum = require("../spotify/getAlbum"); const getTrack = require("../spotify/getTrack"); const { error } = require("@hapi/joi/lib/base"); -module.exports = async (userToken, idOeuvre, {userRepository, oeuvreFavRepository, accessTokenManager,spotifyRepository}) =>{ +module.exports = async (userToken, idOeuvre, type, {userRepository, oeuvreFavRepository, accessTokenManager,spotifyRepository}) =>{ const idUtilisateur = accessTokenManager.decode(userToken)?.value if(! await userRepository.getByUser(idUtilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - - //chercher album - // sinon cherche track - let oeuvre - try { - oeuvre = await getAlbum(idOeuvre, {spotifyRepository}); - } catch (errorAlbum) { - - try { - oeuvre = await getTrack(idOeuvre, {spotifyRepository}); - } catch (errorTrack) { - throwStatusCode(404, "L'ID de l'oeuvre est introuvable"); - } - } + const oeuvre = spotifyRepository.getOeuvre(idOeuvre, type) + if(oeuvre.error) + throwStatusCode(oeuvre.error.status,oeuvre.error.message) if (!(await oeuvreFavRepository.oeuvreFavExists(idUtilisateur, idOeuvre))){ if (!(await oeuvreFavRepository.ajoutPossible(idUtilisateur))) throwStatusCode(403,"Vous avez atteints le nombre maximal d'oeuvres favorites") - - await oeuvreFavRepository.addOeuvrefav(idUtilisateur,idOeuvre) + await oeuvreFavRepository.addOeuvrefav(idUtilisateur,idOeuvre,type) return true } diff --git a/lib/domain/entity/UserEntity.js b/lib/domain/entity/UserEntity.js index dffdb6a..03763ed 100644 --- a/lib/domain/entity/UserEntity.js +++ b/lib/domain/entity/UserEntity.js @@ -1,69 +1,128 @@ -const Joi = require('joi') -const validationErrror = require("./utils/validationError") +const Joi = require("joi"); +const validationErrror = require("./utils/validationError"); const userSignIn = Joi.object().keys({ - email: Joi.string().max(40).required(), - password: Joi.string().max(30).required(), -}) + email: Joi.string().max(40).required(), + password: Joi.string().max(30).required(), +}); const userSignUp = Joi.object().keys({ - pseudo: Joi.string().min(3).max(15).required().custom((value, helpers) => { - if (value.includes('@')) { - return helpers.error('any.invalid'); - } - return value; - }, 'pseudo validation').error(validationErrror("pseudo","le pseudo doit être compris entre 3 et 15 caractère")), - alias: Joi.string().min(3).max(15).error(validationErrror("alias","l'alias doit être compris entre 3 et 15 caractère")), - photo: Joi.string().max(500), - confirmToken: Joi.string().max(50), - bio:Joi.string().min(0).max(1500).error(validationErrror("bio","la bio doit faire moins de 1500 caractères")), -}) + pseudo: Joi.string() + .min(3) + .max(15) + .required() + .custom((value, helpers) => { + if (value.includes("@")) { + return helpers.error("any.invalid"); + } + return value; + }, "pseudo validation") + .error( + validationErrror( + "pseudo", + "le pseudo doit être compris entre 3 et 15 caractère" + ) + ), + alias: Joi.string() + .min(3) + .max(15) + .error( + validationErrror( + "alias", + "l'alias doit être compris entre 3 et 15 caractère" + ) + ), + photo: Joi.string().max(500), + confirmToken: Joi.string().max(50), + bio: Joi.string() + .min(0) + .max(1500) + .error( + validationErrror("bio", "la bio doit faire moins de 1500 caractères") + ), +}); const uploadPreview = Joi.object().keys({ - file: Joi.any() -}) + file: Joi.any(), +}); const createUser = Joi.object().keys({ - email: Joi.string().email().min(10).max(40).required(), - password: Joi.string().max(30).required(), -}) + email: Joi.string().email().min(10).max(40).required(), + password: Joi.string().max(30).required(), +}); const authWithSpotify = Joi.object().keys({ - spotify_code: Joi.string().max(1000).required(), - callback: Joi.string().max(100).required() -}) + spotify_code: Joi.string().max(1000).required(), + callback: Joi.string().max(100).required(), +}); const isUser = Joi.object().keys({ - pseudo: Joi.string().min(3).max(15).required(), -}) + pseudo: Joi.string().max(15).required(), +}); const getUserByConfirmToken = Joi.object().keys({ - confirmToken: Joi.string().max(50).required(), -}) + confirmToken: Joi.string().max(50).required(), +}); const sendResetEmail = Joi.object().keys({ - email: Joi.string().email().min(10).max(40) -}) + email: Joi.string().email().min(10).max(40), +}); const resetPassword = Joi.object().keys({ - resetToken: Joi.string().max(50).required(), - password: Joi.string().min(8).max(30).required().error(validationErrror("password","le mot de passe doit être compris entre 8 et 30 caractères")) -}) + resetToken: Joi.string().max(50).required(), + password: Joi.string() + .min(8) + .max(30) + .required() + .error( + validationErrror( + "password", + "le mot de passe doit être compris entre 8 et 30 caractères" + ) + ), +}); const follow = Joi.object().keys({ - artistId: Joi - .string() - .min(1) - .required(), -}) + artistId: Joi.string().min(1).required(), +}); const oeuvreFav = Joi.object().keys({ - idOeuvre: Joi - .string() - .min(1) - .required(), -}) + idOeuvre: Joi.string().min(1).required(), + type: Joi.string() + .required() + .custom((value, helpers) => { + const allowedValues = ["track", "album", "single"]; + return allowedValues.includes(value) + ? value + : helpers.error("any.invalid"); + }), +}); +const getPageQuery = Joi.object().keys({ + page: Joi.number().integer().min(1).required(), + pageSize: Joi.number().integer().min(1).required(), + orderByLike: Joi.boolean(), +}); + +const getPageParams = Joi.object().keys({ + id: Joi.string().min(1).max(50).required(), +}); + +const modifyPayload = Joi.object().keys({ + photo: Joi.any(), + bio: Joi.string() + .min(0) + .max(200) + .error( + validationErrror("bio", "la bio doit faire moins de 200 caractères") + ), + pseudo: Joi.string().min(3).max(15), + alias: Joi.string().min(3).max(15), + isPrivate: Joi.boolean(), +}); module.exports = { - userSignIn, - userSignUp, - uploadPreview, - createUser, - isUser, - getUserByConfirmToken, - sendResetEmail, - resetPassword, - follow, - authWithSpotify, - oeuvreFav -} \ No newline at end of file + userSignIn, + userSignUp, + uploadPreview, + createUser, + isUser, + getUserByConfirmToken, + sendResetEmail, + resetPassword, + follow, + authWithSpotify, + oeuvreFav, + getPageQuery, + getPageParams, + modifyPayload, +}; diff --git a/lib/domain/model/OeuvreFav.js b/lib/domain/model/OeuvreFav.js new file mode 100644 index 0000000..2c8f438 --- /dev/null +++ b/lib/domain/model/OeuvreFav.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = class { + + constructor(oeuvreFavRaw) { + this.id_oeuvre = oeuvreFavRaw?.id_oeuvre + this.id_utilisateur = oeuvreFavRaw?.id_utilisateur + this.type = oeuvreFavRaw?.type + } +} \ No newline at end of file diff --git a/lib/domain/model/Track.js b/lib/domain/model/Track.js index 56eb162..722833b 100644 --- a/lib/domain/model/Track.js +++ b/lib/domain/model/Track.js @@ -1,16 +1,17 @@ module.exports = class { - constructor(track) { - this.id = track.id - this.name = track.name - this.album = track.album - this.artists = track.artists - this.duration_ms = track.duration_ms - this.popularity = track.popularity - this.spotify_url = track.spotify_url - this.rating = track.rating - this.reviewCount = track.reviewCount - this.likeCount = track.likeCount - this.type = "track" - - } -} \ No newline at end of file + constructor(track) { + this.id = track.id; + this.name = track.name; + this.album = track.album; + this.artists = track.artists; + this.duration_ms = track.duration_ms; + this.popularity = track.popularity; + this.spotify_url = track.spotify_url; + this.rating = track.rating; + this.reviewCount = track.reviewCount; + this.likeCount = track.likeCount; + this.release_date = track.release_date; + this.image = track.album?.image; + this.type = "track"; + } +}; diff --git a/lib/domain/model/User.js b/lib/domain/model/User.js index 2f107c5..b21948c 100644 --- a/lib/domain/model/User.js +++ b/lib/domain/model/User.js @@ -5,8 +5,12 @@ module.exports = class { constructor(userRaw) { this.id_utilisateur = userRaw?.id_utilisateur this.pseudo = userRaw?.pseudo + this.bio = userRaw?.bio this.email = userRaw?.email this.alias = userRaw?.alias + this.following_count = userRaw?.following_count + this.follower_count = userRaw?.follower_count + this.review_count = userRaw?.review_count this.photo = userRaw?.photo this.photo_temporaire = userRaw?.photo_temporaire this.token = userRaw?.token diff --git a/lib/domain/model/UserPublic.js b/lib/domain/model/UserPublic.js index c21c9dc..796d81b 100644 --- a/lib/domain/model/UserPublic.js +++ b/lib/domain/model/UserPublic.js @@ -1,19 +1,29 @@ -'use strict'; - +"use strict"; +const formatPhoto = (photo) => { + if ( + !photo || + "https://" === photo.substring(0, 8) || + "http://" === photo.substring(0, 7) + ) + return photo; + return `${process.env.API_URL}/${photo}`; +}; module.exports = class { - - constructor(userRaw) { - this.id_utilisateur = userRaw?.id_utilisateur - this.pseudo = userRaw?.pseudo; - this.email = userRaw?.email; - this.alias = userRaw?.alias - this.photo = userRaw?.photo - this.bio = userRaw?.bio - this.photo_temporaire = userRaw?.photo_temporaire - this.id_role = userRaw?.id_role; - this.ban_until = userRaw?.ban_until - this.is_private = userRaw?.is_private - this.type = 'user' - } - -}; \ No newline at end of file + constructor(userRaw) { + this.id_utilisateur = userRaw?.id_utilisateur; + this.pseudo = userRaw?.pseudo; + this.email = userRaw?.email; + this.alias = userRaw?.alias; + this.photo = formatPhoto(userRaw?.photo); + this.bio = userRaw?.bio; + this.following_count = userRaw?.following_count; + this.follower_count = userRaw?.follower_count; + this.review_count = userRaw?.review_count; + this.photo_temporaire = formatPhoto(userRaw?.photo_temporaire); + this.id_role = userRaw?.id_role; + this.ban_until = userRaw?.ban_until; + this.is_private = userRaw?.is_private; + this.auth_with_spotify = userRaw?.auth_with_spotify; + this.type = "user"; + } +}; diff --git a/lib/infrastructure/config/strategy.js b/lib/infrastructure/config/strategy.js index 63867b4..ea0ea4d 100644 --- a/lib/infrastructure/config/strategy.js +++ b/lib/infrastructure/config/strategy.js @@ -6,8 +6,6 @@ module.exports = ({userRepository}) =>{ iss: 'urn:issuer:test', sub: false, nbf: true, - exp: true, - maxAgeSec: 14400, // 4 hours timeSkewSec: 15 }, validate: async (artifacts, request, h) => { diff --git a/lib/infrastructure/orm/sequelize/models/OeuvreFavorite.js b/lib/infrastructure/orm/sequelize/models/OeuvreFavorite.js index 50cfde8..7d4f8dd 100644 --- a/lib/infrastructure/orm/sequelize/models/OeuvreFavorite.js +++ b/lib/infrastructure/orm/sequelize/models/OeuvreFavorite.js @@ -17,7 +17,11 @@ module.exports = (sequelize) => { model: 'utilisateur', key: 'id_utilisateur', }, - } + }, + type: { + type: DataTypes.STRING(50), + allowNull: false, + }, }, { freezeTableName: true,}); diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index 679de76..7a68beb 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -1,33 +1,31 @@ -'use strict'; +"use strict"; -const sequelize = require('../orm/sequelize/sequelize'); -const Friend = require('../../domain/model/Friend'); -const User = require('../../domain/model/UserPublic'); -const FriendRepositoryAbstract = require('./interfaces/FriendRepositoryAbstract'); -const { Op } = require('sequelize'); - -module.exports = class extends FriendRepositoryAbstract { +const sequelize = require("../orm/sequelize/sequelize"); +const Friend = require("../../domain/model/Friend"); +const User = require("../../domain/model/User"); +const FriendRepositoryAbstract = require("./interfaces/FriendRepositoryAbstract"); +const { Op } = require("sequelize"); +module.exports = class extends FriendRepositoryAbstract { constructor() { - super(); + super(); this.db = sequelize; - this.model = this.db.model('amis'); - this.UserModel = this.db.model('utilisateur'); + this.model = this.db.model("amis"); + this.UserModel = this.db.model("utilisateur"); } async persist(friendEntity) { - friendEntity.createdAt = sequelize.literal('CURRENT_TIMESTAMP'); - friendEntity.updatedAt = sequelize.literal('CURRENT_TIMESTAMP'); + friendEntity.createdAt = sequelize.literal("CURRENT_TIMESTAMP"); + friendEntity.updatedAt = sequelize.literal("CURRENT_TIMESTAMP"); const user = await this.model.create(friendEntity); user.save(); return this.createAmis(user); } - async getById(id_utilisateur, amiIdUtilisateur) { const user = await this.model.findOne({ - where: { - id_utilisateur : id_utilisateur, - amiIdUtilisateur : amiIdUtilisateur + where: { + id_utilisateur: id_utilisateur, + amiIdUtilisateur: amiIdUtilisateur, }, }); return this.createAmis(user); @@ -39,27 +37,29 @@ module.exports = class extends FriendRepositoryAbstract { async getListFriendsById(id) { const idFriends = await this.model.findAll({ - attributes: ['amiIdUtilisateur'], + attributes: ["amiIdUtilisateur"], where: { id_utilisateur: id, - en_attente: false + en_attente: false, }, }); let friends = await this.UserModel.findAll({ where: { - id_utilisateur: { [Op.in]: idFriends.map((f)=> f.getDataValue("amiIdUtilisateur"))} - } - }) - return friends ? friends.map( f => new User(f)) : null; + id_utilisateur: { + [Op.in]: idFriends.map((f) => f.getDataValue("amiIdUtilisateur")), + }, + }, + }); + return friends ? friends.map((f) => new User(f)) : null; } async removeFriendById(id_utilisateur, amiIdUtilisateur) { const user = await this.model.destroy({ where: { id_utilisateur: id_utilisateur, - amiIdUtilisateur: amiIdUtilisateur - } + amiIdUtilisateur: amiIdUtilisateur, + }, }); return user; } @@ -68,49 +68,105 @@ module.exports = class extends FriendRepositoryAbstract { const idFriends = await this.model.findAll({ where: { en_attente: true, - amiIdUtilisateur: id + amiIdUtilisateur: id, }, }); const friends = await this.UserModel.findAll({ where: { - id_utilisateur: { [Op.in]: idFriends.map((f)=> f.getDataValue("amiIdUtilisateur"))} - } - }) - return friends ? friends.map( f => new User(f)) : null; + id_utilisateur: { + [Op.in]: idFriends.map((f) => f.getDataValue("amiIdUtilisateur")), + }, + }, + }); + return friends ? friends.map((f) => new User(f)) : null; } async accept(id, amiIdUtilisateur) { const user = await this.model.update( { en_attente: false, - updatedAt: sequelize.literal('CURRENT_TIMESTAMP') + updatedAt: sequelize.literal("CURRENT_TIMESTAMP"), }, { where: { id_utilisateur: id, - amiIdUtilisateur: amiIdUtilisateur - } + amiIdUtilisateur: amiIdUtilisateur, + }, } ); return this.createAmis(user); } async areFriends(id, amiIdUtilisateur) { - if(id === amiIdUtilisateur) return true + if (id == amiIdUtilisateur) return true; + const count = await this.model.count({ + where: { + [Op.or]: [ + { + id_utilisateur: id, + amiIdUtilisateur: amiIdUtilisateur, + en_attente: false, + }, + { + id_utilisateur: amiIdUtilisateur, + amiIdUtilisateur: id, + en_attente: false, + }, + ], + }, + }); + return count > 0; + } + async doesFollows(id, amiIdUtilisateur) { + if (id == amiIdUtilisateur) return true; const count = await this.model.count({ where: { [Op.or]: [ - { id_utilisateur: id, + { + id_utilisateur: id, amiIdUtilisateur: amiIdUtilisateur, - en_attente: false + en_attente: false, }, - { id_utilisateur: amiIdUtilisateur, + { + id_utilisateur: amiIdUtilisateur, amiIdUtilisateur: id, - en_attente: false + en_attente: false, }, - ] + ], + }, + }); + return count > 0; + } + async getFollowInfo(id_utilisateur, amiIdUtilisateur) { + const areFriends = await this.areFriends(id_utilisateur, amiIdUtilisateur); + const relation1 = await this.model.findOne({ + where: { + id_utilisateur: id_utilisateur, + amiIdUtilisateur: amiIdUtilisateur, + }, + }); + + const relation2 = await this.model.findOne({ + where: { + //if the other follows the user + id_utilisateur: amiIdUtilisateur, + amiIdUtilisateur: id_utilisateur, }, }); - return count > 0 + return areFriends + ? { + areFriends: true, + doesFollows: true, + isWaiting: false, + isWaited: true, + isFollowed: true, + } + : { + areFriends: false, + doesFollows: !!relation1, + isWaiting: relation1 ? !!relation1.en_attente : false, + isWaited: relation2 ? !!relation2.en_attente : false, + isFollowed: !!relation2, + }; } }; diff --git a/lib/infrastructure/repositories/OeuvreFavRepository.js b/lib/infrastructure/repositories/OeuvreFavRepository.js index 1f7234f..6b8ad83 100644 --- a/lib/infrastructure/repositories/OeuvreFavRepository.js +++ b/lib/infrastructure/repositories/OeuvreFavRepository.js @@ -3,7 +3,7 @@ const sequelize = require('../orm/sequelize/sequelize'); const { Op } = require('sequelize'); -const { assert } = require('joi'); +const OeuvreFav = require('../../domain/model/OeuvreFav.js'); const OeuvreFavRepositoryAbstract = require('./interfaces/OeuvreFavRepositoryAbstract'); module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract @@ -14,7 +14,9 @@ module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract this.oeuvreFav = this.db.model('oeuvre_favorite'); this.oeuvresFavMax = 3; } - + createOeuvre(oeuvre) { + return oeuvre ? new OeuvreFav(oeuvre.dataValues) : null + } async ajoutPossible(idUtilisateur){ let nbOeuvresFav = await this.oeuvreFav.count({ where: { @@ -24,13 +26,14 @@ module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract return nbOeuvresFav < this.oeuvresFavMax; } - async addOeuvrefav(idUtilisateur, idOeuvre) { - + async addOeuvrefav(idUtilisateur, idOeuvre,type) { + if(!await this.ajoutPossible(idUtilisateur)) return false const seqUser = await this.oeuvreFav.create({ id_utilisateur: idUtilisateur, id_oeuvre: idOeuvre, createdAt: sequelize.literal('CURRENT_TIMESTAMP'), - updatedAt: sequelize.literal('CURRENT_TIMESTAMP') + updatedAt: sequelize.literal('CURRENT_TIMESTAMP'), + type }) await seqUser.save() } @@ -39,7 +42,7 @@ module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract const seqUser = await this.oeuvreFav.destroy({ where: { id_utilisateur: idUtilisateur, - id_oeuvre: idOeuvre + id_oeuvre: idOeuvre, } }) } @@ -49,7 +52,7 @@ module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract where: { [Op.and]: { id_utilisateur: idUtilisateur, - id_oeuvre: idOeuvre + id_oeuvre: idOeuvre, } } }) @@ -62,7 +65,7 @@ module.exports = class extends OeuvreFavRepositoryAbstract { // creer abstract id_utilisateur: idUtilisateur }, }); - return oeuvresFav ? oeuvresFav.map(oeuvre => oeuvre.id_oeuvre) : null; + return oeuvresFav ? oeuvresFav.map(oeuvre => this.createOeuvre(oeuvre)) : null; } }; diff --git a/lib/infrastructure/repositories/UserRepository.js b/lib/infrastructure/repositories/UserRepository.js index f8afd99..1178ac9 100644 --- a/lib/infrastructure/repositories/UserRepository.js +++ b/lib/infrastructure/repositories/UserRepository.js @@ -36,7 +36,7 @@ module.exports = class extends userRepository { createUser(seqUser){ if(!seqUser) return null - return new user(seqUser); + return new user(seqUser.dataValues); } async getByEmailOrPseudo(email, pseudo){ const seqUser = await this.model.findOne({ @@ -54,11 +54,28 @@ module.exports = class extends userRepository { let request = {where: {pseudo : {[Op.like]: `${pseudo}%`}}} if(limit) request.limit = limit let seqUsers = await this.model.findAll(request); - seqUsers = seqUsers.map(item => this.createUser(item.dataValues)) + seqUsers = seqUsers.map(item => this.createUser(item)) return Object.values(seqUsers); } async getByUser(id){ - const seqUser = await this.model.findOne({ + const seqUser = await this.model.findOne( + { + attributes: { + include: [ + [ + sequelize.literal('(SELECT COUNT(*) FROM amis WHERE amis.id_utilisateur = utilisateur.id_utilisateur AND en_attente=false)'), + 'following_count' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM amis WHERE amis.amiIdUtilisateur = utilisateur.id_utilisateur AND en_attente=false)'), + 'follower_count' + ], + [ + sequelize.literal('(SELECT COUNT(*) FROM review WHERE review.id_utilisateur = utilisateur.id_utilisateur)'), + 'review_count' + ], + ] + }, where: { id_utilisateur : id } diff --git a/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js index 8382a30..59d5e4e 100644 --- a/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js +++ b/lib/infrastructure/repositories/interfaces/FriendRepositoryAbstract.js @@ -1,30 +1,28 @@ -'use strict'; +"use strict"; module.exports = class { - persist(friendEntity) { - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } getById(id_utilisateur, amiIdUtilisateur) { - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } - - getListFriendsById(id){ - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + getListFriendsById(id) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } - removeFriendById(id_utilisateur, amiIdUtilisateur){ - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + removeFriendById(id_utilisateur, amiIdUtilisateur) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } - getRequestFriendsById(id){ - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + getRequestFriendsById(id) { + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } - + accept(id, amiIdUtilisateur) { - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } areFriends(id, amiIdUtilisateur) { - throw new Error('ERR_METHOD_NOT_IMPLEMENTED'); + throw new Error("ERR_METHOD_NOT_IMPLEMENTED"); } }; diff --git a/lib/infrastructure/security/JwtAccessTokenManager.js b/lib/infrastructure/security/JwtAccessTokenManager.js index 27b20ce..fd297f5 100644 --- a/lib/infrastructure/security/JwtAccessTokenManager.js +++ b/lib/infrastructure/security/JwtAccessTokenManager.js @@ -4,7 +4,6 @@ const jwt = require('jsonwebtoken'); const AccessTokenManager = require('./AccessTokenManager'); const JWT_SECRET_KEY = process.env.SECRET_ENCODER; - module.exports = class extends AccessTokenManager { generate(user) { @@ -13,8 +12,7 @@ module.exports = class extends AccessTokenManager { value: user.id_utilisateur, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user aud: 'urn:audience:test', // needs to match definition above iss: 'urn:issuer:test', // needs to match definition above, - // expiresIn: '365d' - }, JWT_SECRET_KEY); + }, JWT_SECRET_KEY,{ expiresIn: '999y' }); } decode(accessToken) { diff --git a/lib/interfaces/controllers/UsersController.js b/lib/interfaces/controllers/UsersController.js index c9606d6..158a13d 100644 --- a/lib/interfaces/controllers/UsersController.js +++ b/lib/interfaces/controllers/UsersController.js @@ -1,191 +1,249 @@ -'use strict'; +"use strict"; -const CreateUser = require('../../application/use_cases/user/CreateUser'); -const AuthWithSpotify = require('../../application/use_cases/user/AuthWithSpotify'); -const CompleteAccount = require('../../application/use_cases/user/CompleteAccount'); -const GetAccessToken = require('../../application/use_cases/security/GetAccessToken'); -const handleError = require("./utils/handleError") -const uploadPreview = require("../../application/use_cases/user/uploadPreview") -const getUserByPseudo = require("../../application/use_cases/user/getUserByPseudo") -const getUserByConfirmToken = require("../../application/use_cases/user/getUserByConfirmToken") -const sendResetEmail = require("../../application/use_cases/user/sendResetEmail") -const resetPassword = require("../../application/use_cases/user/resetPassword") -const changePrivateStatus = require("../../application/use_cases/user/changePrivateStatus") -const refreshToken = require("../../application/use_cases/spotify/RefreshToken") +const CreateUser = require("../../application/use_cases/user/CreateUser"); +const AuthWithSpotify = require("../../application/use_cases/user/AuthWithSpotify"); +const CompleteAccount = require("../../application/use_cases/user/CompleteAccount"); +const GetAccessToken = require("../../application/use_cases/security/GetAccessToken"); +const handleError = require("./utils/handleError"); +const uploadPreview = require("../../application/use_cases/user/uploadPreview"); +const getUserByPseudo = require("../../application/use_cases/user/getUserByPseudo"); +const getUserByConfirmToken = require("../../application/use_cases/user/getUserByConfirmToken"); +const sendResetEmail = require("../../application/use_cases/user/sendResetEmail"); +const resetPassword = require("../../application/use_cases/user/resetPassword"); +const changePrivateStatus = require("../../application/use_cases/user/changePrivateStatus"); +const refreshToken = require("../../application/use_cases/spotify/RefreshToken"); const throwStatusCode = require("../../application/use_cases/utils/throwStatusCode"); const follow = require("../../application/use_cases/user/follow"); const oeuvreFav = require("../../application/use_cases/user/oeuvreFav"); const getOeuvresFav = require("../../application/use_cases/user/getOeuvresFav"); - -module.exports = { - - async confirmUser(request, handler) { - try{ - // Context - const serviceLocator = request.server.app.serviceLocator; - // Input +const getPage = require("../../application/use_cases/user/getPage"); +const modifyProfile = require("../../application/use_cases/user/modifyProfile"); - let { pseudo, photo, alias, bio,confirmToken } = request.payload - return handler.response(await CompleteAccount(pseudo, alias, bio,photo,confirmToken, serviceLocator)).code(200) - }catch(error){ - return handleError(error) - } - }, +module.exports = { + async confirmUser(request, handler) { + try { + // Context + const serviceLocator = request.server.app.serviceLocator; + // Input - async signIn(request,handler){ + let { pseudo, photo, alias, bio, confirmToken } = request.payload; + return handler + .response( + await CompleteAccount( + pseudo, + alias, + bio, + photo, + confirmToken, + serviceLocator + ) + ) + .code(200); + } catch (error) { + return handleError(error); + } + }, - const serviceLocator = request.server.app.serviceLocator; - const {email, password} = request.payload - try{ - return handler.response(await GetAccessToken(email,password,serviceLocator)).code(200) - } - catch (error){ - return handleError(error) - } - }, - async uploadPreviewProfile(request, handler){ - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token] = authorizationHeader.split(' '); - const {file} = request.payload - try{ - return handler.response({ - path: `${request.server.info.uri}/${await uploadPreview(file,token, serviceLocator)}` - }).code(200) - } - catch (e){ - return handleError(e) - } - }, - async authWithSpotify(request, handler){ - const serviceLocator = request.server.app.serviceLocator; - const {spotify_code,callback} = request.payload - try{ - const returnValue = await AuthWithSpotify(spotify_code,callback,serviceLocator); - if(returnValue.idUtilisateur){ - refreshToken(returnValue.idUtilisateur,false,serviceLocator) - delete returnValue.idUtilisateur - } - return handler.response(returnValue).code(200) + async signIn(request, handler) { + console.log("tesssssssssssssssssst"); + const serviceLocator = request.server.app.serviceLocator; + const { email, password } = request.payload; + try { + return handler + .response(await GetAccessToken(email, password, serviceLocator)) + .code(200); + } catch (error) { + return handleError(error); + } + }, + async uploadPreviewProfile(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { file } = request.payload; + try { + return handler + .response({ + path: `${request.server.info.uri}/${await uploadPreview( + file, + token, + serviceLocator + )}`, + }) + .code(200); + } catch (e) { + return handleError(e); + } + }, + async authWithSpotify(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { spotify_code, callback } = request.payload; + try { + const returnValue = await AuthWithSpotify( + spotify_code, + callback, + serviceLocator + ); + if (returnValue.idUtilisateur) { + refreshToken(returnValue.idUtilisateur, false, serviceLocator); + delete returnValue.idUtilisateur; + } + return handler.response(returnValue).code(200); + } catch (e) { + return handleError(e); + } + }, + async createUser(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { email, password } = request.payload; + try { + console.log(email, password); + const user = await CreateUser(email, password, serviceLocator); - } - catch (e){ - return handleError(e) - } - }, - async createUser(request, handler){ - const serviceLocator = request.server.app.serviceLocator; - const {email, password} = request.payload - try{ - console.log(email, password) - const user = await CreateUser(email,password,serviceLocator); + return handler.response("compte créé").code(200); + } catch (e) { + return handleError(e); + } + }, + async isUser(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { pseudo } = request.query; + try { + const user = await getUserByPseudo(pseudo, serviceLocator); + return handler.response({ isUser: !!user }).code(200); + } catch (e) { + return handleError(e); + } + }, + async getUserByConfirmToken(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { confirmToken } = request.query; + try { + const user = await getUserByConfirmToken(confirmToken, serviceLocator); + if (!user) throwStatusCode(403, "no user"); + return handler.response(user).code(200); + } catch (e) { + return handleError(e); + } + }, + async sendResetEmail(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { email } = request.payload; + try { + await sendResetEmail(email, serviceLocator); + return handler.response("un email a été envoyé").code(200); + } catch (e) { + return handleError(e); + } + }, + async resetPassword(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const { password, resetToken } = request.payload; + try { + await resetPassword(password, resetToken, serviceLocator); + return handler.response("un email a été envoyé").code(200); + } catch (e) { + return handleError(e); + } + }, + async follow(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { artistId } = request.payload; + try { + const returnValue = (await follow(token, artistId, serviceLocator)) + ? "artiste suivi" + : "vous avez arrêté de suivre l'artiste"; + return handler.response(returnValue).code(200); + } catch (error) { + return handleError(error); + } + }, + async changePrivateStatus(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + try { + await changePrivateStatus(token, serviceLocator); + return handler + .response("Le statut du compte a bien été mis à jour") + .code(200); + } catch (e) { + return handleError(e); + } + }, + async oeuvreFav(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { idOeuvre, type } = request.payload; + console.log(request.payload); + try { + // await oeuvreFav(idOeuvre,serviceLocator); + const returnValue = (await oeuvreFav( + token, + idOeuvre, + type, + serviceLocator + )) + ? "L'oeuvre a été rajoutée en favori" + : "L'oeuvre a été retirée de vos favori"; + return handler.response(returnValue).code(200); + } catch (e) { + return handleError(e); + } + }, - return handler.response("compte créé").code(200) - } - catch (e){ - return handleError(e) - } - }, - async isUser(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const {pseudo} = request.query - try{ - const user = await getUserByPseudo(pseudo,serviceLocator); - return handler.response({isUser: !!user}).code(200) - } - catch (e){ - return handleError(e) - } - }, - async getUserByConfirmToken(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const {confirmToken} = request.query - try{ - const user = await getUserByConfirmToken(confirmToken,serviceLocator); - if(!user) - throwStatusCode(403,"no user") - return handler.response(user).code(200) - } - catch (e){ - return handleError(e) - } - }, - async sendResetEmail(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const {email} = request.payload - try{ - await sendResetEmail(email,serviceLocator); - return handler.response("un email a été envoyé").code(200) - } - catch (e){ - return handleError(e) - } - }, - async resetPassword(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const {password,resetToken} = request.payload - try{ - await resetPassword(password,resetToken,serviceLocator); - return handler.response("un email a été envoyé").code(200) - } - catch (e){ - return handleError(e) - } - }, - async follow(request,handler) { - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token] = authorizationHeader.split(' '); - const {artistId} = request.payload - try{ - const returnValue = await follow(token,artistId,serviceLocator) - ? "artiste suivi" - : "vous avez arrêté de suivre l'artiste" - return handler.response(returnValue).code(200) - }catch(error){ - return handleError(error) - } - }, - async changePrivateStatus(request,handler){ - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token] = authorizationHeader.split(' '); - try{ - await changePrivateStatus(token,serviceLocator); - return handler.response("Le statut du compte a bien été mis à jour").code(200) - } - catch (e){ - return handleError(e) - } - }, - async oeuvreFav(request, handler){ - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token] = authorizationHeader.split(' '); - const {idOeuvre} = request.payload - try{ - // await oeuvreFav(idOeuvre,serviceLocator); - const returnValue = await oeuvreFav(token,idOeuvre,serviceLocator) - ? "L'oeuvre a été rajoutée en favori" - : "L'oeuvre a été retirée de vos favori" - return handler.response(returnValue).code(200) - } - catch (e){ - return handleError(e) - } - }, + async getOeuvresFav(request, handler) { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + try { + const arrayOeuvresFav = await getOeuvresFav(token, serviceLocator); - async getOeuvresFav(request, handler){ - const serviceLocator = request.server.app.serviceLocator; - const authorizationHeader = request.headers.authorization; - const [, token] = authorizationHeader.split(' '); - try{ - const arrayOeuvresFav = await getOeuvresFav(token,serviceLocator) - - return handler.response(arrayOeuvresFav).code(200) - } - catch (e){ - return handleError(e) - } + return handler.response(arrayOeuvresFav).code(200); + } catch (e) { + return handleError(e); + } + }, + async getPage(request, handler) { + try { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { page, pageSize, orderByLike } = request.query; + const { id } = request.params; + return handler + .response( + await getPage(id, page, pageSize, orderByLike, token, serviceLocator) + ) + .code(200); + } catch (e) { + return handleError(e); + } + }, + async modify(request, handler) { + try { + const serviceLocator = request.server.app.serviceLocator; + const authorizationHeader = request.headers.authorization; + const [, token] = authorizationHeader.split(" "); + const { photo, pseudo, bio, alias, isPrivate } = request.payload; + return handler + .response( + await modifyProfile( + photo, + pseudo, + bio, + alias, + isPrivate, + token, + serviceLocator + ) + ) + .code(200); + } catch (e) { + return handleError(e); } + }, }; diff --git a/lib/interfaces/routes/users.js b/lib/interfaces/routes/users.js index 8de4fc9..dc66e97 100644 --- a/lib/interfaces/routes/users.js +++ b/lib/interfaces/routes/users.js @@ -10,7 +10,10 @@ const { resetPassword, follow, authWithSpotify, - oeuvreFav + oeuvreFav, + getPageQuery, + getPageParams, + modifyPayload } = require('../../domain/entity/UserEntity') const UsersController = require('../controllers/UsersController'); const MAX_BYTE_SIZE =20971520 @@ -325,10 +328,67 @@ module.exports = { 503: {description : 'Service unavailable'}, } } - } + } } }, - + { + method: 'GET', + path: '/users/{id}/page', + handler: UsersController.getPage, + options: { + description: 'get artist page', + tags: ['api'], + auth: 'jwt', + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + query: getPageQuery, + params: getPageParams + } + } + }, + { + method: 'POST', + path: '/users/modify', + handler: UsersController.modify, + options: { + description: 'modify user info', + tags: ['api'], + auth: 'jwt', + payload: { + maxBytes: MAX_BYTE_SIZE, // Set your desired maximum payload size in bytes + output: 'stream', + parse: true, + allow: 'multipart/form-data', + multipart: true, // Set multipart to true for handling file uploads + }, + plugins: { + 'hapi-swagger': { + responses: { + 200: {description: 'Success'}, + 401: {description : 'Unauthorized'}, + 403: {description : 'forbidden'}, + 404: {description : 'Ressource not found'}, + 500: {description: 'Internal server error'}, + 503: {description : 'Service unavailable'}, + } + } + }, + validate: { + payload: modifyPayload, + } + } + } ]); } }; \ No newline at end of file diff --git a/lib/interfaces/serializers/ArtistSerializer.js b/lib/interfaces/serializers/ArtistSerializer.js index 15fde98..24f1358 100644 --- a/lib/interfaces/serializers/ArtistSerializer.js +++ b/lib/interfaces/serializers/ArtistSerializer.js @@ -1,14 +1,17 @@ -const Artist = require("../../domain/model/Artist") +const Artist = require("../../domain/model/Artist"); const serializeArtiste = (artisteRaw) => { - const artist = { - id: artisteRaw.id, - name: artisteRaw.name, - follower_count: artisteRaw?.follower_count, - image: artisteRaw?.images && artisteRaw.images.length > 0 ? artisteRaw.images[0].url : null, - spotify_url : artisteRaw?.external_urls?.spotify, - popularity: artisteRaw?.popularity, - genres : artisteRaw?.genres - } - return new Artist(artist) -} -module.exports=serializeArtiste \ No newline at end of file + const artist = { + id: artisteRaw.id, + name: artisteRaw.name, + follower_count: artisteRaw?.follower_count, + image: + artisteRaw?.images && artisteRaw.images.length > 0 + ? artisteRaw.images[0].url + : null, + spotify_url: artisteRaw?.external_urls?.spotify, + popularity: artisteRaw?.popularity, + genres: artisteRaw?.genres, + }; + return new Artist(artist); +}; +module.exports = serializeArtiste; diff --git a/lib/interfaces/serializers/OeuvreSerializer.js b/lib/interfaces/serializers/OeuvreSerializer.js new file mode 100644 index 0000000..9899f3d --- /dev/null +++ b/lib/interfaces/serializers/OeuvreSerializer.js @@ -0,0 +1,20 @@ +const artistSerializer = require("./ArtistSerializer"); +const trackSerializer = require("./TrackSerializer"); +const albumSerializer = require("./AlbumSerializer"); +const oeuvreSerializer = (rawOeuvre, type) => { + console.log(type); + switch (type) { + case "artist": + return artistSerializer(rawOeuvre); + case "track": + return trackSerializer(rawOeuvre); + case "album": + return albumSerializer(rawOeuvre); + case "single": + return albumSerializer(rawOeuvre); + default: + return null; + } +}; + +module.exports = oeuvreSerializer; diff --git a/lib/interfaces/serializers/TrackSerializer.js b/lib/interfaces/serializers/TrackSerializer.js index 88007a2..41f6af9 100644 --- a/lib/interfaces/serializers/TrackSerializer.js +++ b/lib/interfaces/serializers/TrackSerializer.js @@ -3,22 +3,25 @@ const Track = require("../../domain/model/Track"); const SerializeAlbum = require("./AlbumSerializer"); const SerializeArtist = require("./ArtistSerializer"); -const serializeTrack = (trackRaw) => { - const album = trackRaw?.album ? SerializeAlbum(trackRaw.album) : undefined - const track = { - id: trackRaw.id, - name: trackRaw.name, - album: album, - artists: trackRaw?.artists ? trackRaw?.artists?.map(item => SerializeArtist(item)) : undefined, - spotify_url : trackRaw.external_urls.spotify, - duration_ms: trackRaw?.duration_ms, - popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, - rating: trackRaw?.rating, - likeCount: trackRaw?.likeCount, - reviewCount: trackRaw?.reviewCount, - }; +const serializeTrack = (trackRaw) => { + const album = trackRaw?.album ? SerializeAlbum(trackRaw.album) : undefined; + const track = { + id: trackRaw.id, + name: trackRaw.name, + album: album, + artists: trackRaw?.artists + ? trackRaw?.artists?.map((item) => SerializeArtist(item)) + : undefined, + spotify_url: trackRaw.external_urls.spotify, + duration_ms: trackRaw?.duration_ms, + popularity: trackRaw?.popularity ? trackRaw?.popularity : undefined, + rating: trackRaw?.rating, + likeCount: trackRaw?.likeCount, + reviewCount: trackRaw?.reviewCount, + release_date: trackRaw?.album?.release_date, + }; - return new Track(track) + return new Track(track); }; -module.exports = serializeTrack \ No newline at end of file +module.exports = serializeTrack; diff --git a/test/integration/fixture/artist/getArtistFixture.js b/test/integration/fixture/artist/getArtistFixture.js index 1f2ae66..abf8c1b 100644 --- a/test/integration/fixture/artist/getArtistFixture.js +++ b/test/integration/fixture/artist/getArtistFixture.js @@ -1,205 +1,210 @@ const mockArtist = { - external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, - genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], - id: '4FpJcNgOvIpSBeJgRg3OfN', - images: [ - { - height: 640, - url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', - width: 640 - }, - ], - name: 'Orelsan', - popularity: 64, - type: 'artist', - } + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + }, + genres: ["french hip hop", "old school rap francais", "rap conscient"], + id: "4FpJcNgOvIpSBeJgRg3OfN", + images: [ + { + height: 640, + url: "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", + width: 640, + }, + ], + name: "Orelsan", + popularity: 64, + type: "artist", +}; const mockAlbumRaw = { - "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", - "items": [ - { - "album_group": "album", - "album_type": "album", - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" - }, - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist" - } - ], - "external_urls": { - "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + href: "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", + items: [ + { + album_group: "album", + album_type: "album", + artists: [ + { + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + }, + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", }, - "id": "68YP0pEgwhnfRqQAzu71gP", - "images": [ - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "width": 640 - } - ], - "name": "Civilisation Edition Ultime", - "release_date": "2022-10-28", - "total_tracks": 25, - "type": "album" - } - ] -} + ], + external_urls: { + spotify: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + }, + id: "68YP0pEgwhnfRqQAzu71gP", + images: [ + { + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + width: 640, + }, + ], + name: "Civilisation Edition Ultime", + release_date: "2022-10-28", + total_tracks: 25, + type: "album", + }, + ], +}; const mockUser = { - id_utilisateur: 1, - pseudo: "John Doe", - alias: "John", - ban_until: null, - email: "testemail@gmail", - id_role: 1, - photo: null, - photo_temporaire: null, - type: "user", - is_private: false -} + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false, +}; const mockUserPrivate = { - pseudo: "John Doe2", - alias: "John", - ban_until: null, - email: "testemail@gmail", - id_role: 1, - id_utilisateur: 1, - photo: null, - photo_temporaire: null, - type: "user", - is_private: true -} -const actualDate = new Date() - + pseudo: "John Doe2", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true, +}; +const actualDate = new Date(); const mockLikedReview = { - "id_review": 25, - "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlike": 32, - "countComment": 0, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "note": 5, - "createdAt": "2024-01-03T00:00:00.000Z", - "updated_at": "2024-01-03T00:00:00.000Z", - "type": "album", - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "token": null, - "refresh_token": null, - "reset_token": null, - "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", - "id_role": 1, - "ban_until": null, - "confirmed": true, - "confirm_token": null, - "auth_with_spotify": false, - "is_private": true, - "type": "user" - } -} + id_review: 25, + id_oeuvre: "68YP0pEgwhnfRqQAzu71gP", + countlikes: 32, + countComment: 0, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + note: 5, + createdAt: "2024-01-03T00:00:00.000Z", + updated_at: "2024-01-03T00:00:00.000Z", + type: "album", + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + email: "Constance.Constance@gmail.com", + auth_with_spotify: false, + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + token: null, + refresh_token: null, + reset_token: null, + password: "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + id_role: 1, + ban_until: null, + confirmed: true, + confirm_token: null, + auth_with_spotify: false, + is_private: true, + type: "user", + }, +}; const mockCommentedReview = { - "id_review": 25, - "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlike": 32, - "countComment": 0, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "note": 5, - "createdAt": "2024-01-03T00:00:00.000Z", - "updated_at": "2024-01-03T00:00:00.000Z", - "type": "album", - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "token": null, - "refresh_token": null, - "reset_token": null, - "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", - "id_role": 1, - "ban_until": null, - "confirmed": true, - "confirm_token": null, - "auth_with_spotify": false, - "is_private": true, - "type": "user" - } -} + id_review: 25, + id_oeuvre: "68YP0pEgwhnfRqQAzu71gP", + countlikes: 32, + countComment: 0, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + note: 5, + createdAt: "2024-01-03T00:00:00.000Z", + updated_at: "2024-01-03T00:00:00.000Z", + type: "album", + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + email: "Constance.Constance@gmail.com", + auth_with_spotify: false, + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + token: null, + refresh_token: null, + reset_token: null, + password: "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + id_role: 1, + ban_until: null, + confirmed: true, + confirm_token: null, + auth_with_spotify: false, + is_private: true, + type: "user", + }, +}; const mockOeuvreReviewSpotify = { - "album_type": "album", - "artists": [ + album_type: "album", + artists: [ { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", }, - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", + }, ], - "external_urls": { - "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + external_urls: { + spotify: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", }, - "genres": [], - "id": "68YP0pEgwhnfRqQAzu71gP", - "images": [ + genres: [], + id: "68YP0pEgwhnfRqQAzu71gP", + images: [ { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "width": 640 - } + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + width: 640, + }, ], - "name": "Civilisation Edition Ultime", - "popularity": 60, - "release_date": "2022-10-28", - "total_tracks": 1, - "tracks": { - "href": "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", - "items": [ - { - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" - }, - "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist", - "uri": "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN" - } - ], - - "disc_number": 1, - "duration_ms": 161732, - "external_urls": { - "spotify": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4" + name: "Civilisation Edition Ultime", + popularity: 60, + release_date: "2022-10-28", + total_tracks: 1, + tracks: { + href: "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", + items: [ + { + artists: [ + { + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", }, - "id": "7b3YQboXo3kau9YVUyx3r4", - "name": "CP_001_ Intro Civilisation Perdue", - "preview_url": "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", - "track_number": 1, - "type": "track" - } + href: "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", + uri: "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN", + }, + ], + + disc_number: 1, + duration_ms: 161732, + external_urls: { + spotify: "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + }, + id: "7b3YQboXo3kau9YVUyx3r4", + name: "CP_001_ Intro Civilisation Perdue", + preview_url: + "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", + track_number: 1, + type: "track", + }, ], - "total": 25 + total: 25, }, - "type": "album" -} - + type: "album", +}; module.exports = { mockUser, @@ -209,4 +214,4 @@ module.exports = { mockLikedReview, mockCommentedReview, mockOeuvreReviewSpotify, -} \ No newline at end of file +}; diff --git a/test/integration/users.test.js b/test/integration/users.test.js index c9b1e01..5f66178 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -1,923 +1,971 @@ -'use strict'; -const Hapi = require('@hapi/hapi'); -const User = require("../../lib/domain/model/User") +"use strict"; +const Hapi = require("@hapi/hapi"); +const User = require("../../lib/domain/model/User"); const bcrypt = require("bcrypt"); const strategy = require("../../lib/infrastructure/config/strategy"); const Jwt = require("@hapi/jwt"); const jwt = require("jsonwebtoken"); -require('dotenv').config() -let server -const mockUserRepository = {} -const mockAccesTokenManager = {} -const mockSpotifyRepository = {} -const mockMailRepository = {} -const mockDocumentRepository = {} -const mockFollowRepository = {} -const mockOeuvreFavRepository = {} -const mockToken = jwt.sign({ - sub: 'my-sub', // needs to match definition above +const { type } = require("@hapi/joi/lib/extend.js"); +require("dotenv").config(); +let server; +const mockUserRepository = {}; +const mockAccesTokenManager = {}; +const mockSpotifyRepository = {}; +const mockMailRepository = {}; +const mockDocumentRepository = {}; +const mockFollowRepository = {}; +const mockOeuvreFavRepository = {}; +const mockToken = jwt.sign( + { + sub: "my-sub", // needs to match definition above value: 1, // this is a custom key I used, it could be named anything. Value should be a way to authenticate the user - aud: 'urn:audience:test', // needs to match definition above - iss: 'urn:issuer:test', // needs to match definition above, - expiresIn: '365d' -}, process.env.SECRET_ENCODER) - -mockAccesTokenManager.generate = ((test) =>{return ''}) -describe('user route', () => { - - beforeEach(async () => { - server = Hapi.server({ - port: process.env.PORT || 3000 - }); - server.app.serviceLocator = { - userRepository: mockUserRepository, - accessTokenManager:mockAccesTokenManager, - spotifyRepository: mockSpotifyRepository, - mailRepository: mockMailRepository, - documentRepository: mockDocumentRepository, - followRepository: mockFollowRepository, - oeuvreFavRepository : mockOeuvreFavRepository, - - } - server.register(Jwt) - server.auth.strategy('jwt', 'jwt', strategy({userRepository: mockUserRepository})); - await server.register([ - require('../../lib/interfaces/routes/users'), - ]); - }); - - afterEach(async () => { - jest.clearAllMocks(); - await server.stop(); - }); - describe("/users/createUser", ()=>{ - afterEach(()=>{ - jest.clearAllMocks(); - }) - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) - mockUserRepository.persist = jest.fn((test) => { - return {id_utilisateur: 1} - }) - mockMailRepository.send = jest.fn(option => null) - mockSpotifyRepository.getToken= jest.fn(()=> { - return {access_token: 1, refresh_token: 1} - }) - it('should respond code 200 with email inscription', async () => { - - const res = await server.inject({ - method: 'POST', - url: '/users/createUser', - payload: { - email: "tesddesqt@gmaiL.com", - password: "somepassword" - } - }); - expect(res.statusCode).toBe(200); - }); - - it('should respond code 403 with already existing email', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> 'something') - const res = await server.inject({ - method: 'POST', - url: '/users/createUser', - payload: { - email: "tesddesqt@gmaiL.com", - password: "somepassword" - } - }); - expect(res.statusCode).toBe(403); - }); - - }) - describe("/users/confirmUser", ()=>{ - afterEach(()=>{ - jest.clearAllMocks(); - }) - - mockUserRepository.getByConfirmToken = jest.fn(()=> { - return { - id_utilisateur:1, - photo_temporaire: "path" - } - }) - mockUserRepository.updateUser= jest.fn(user => user) - mockUserRepository.persist = jest.fn((test) => { - return {id_utilisateur: 1} - }) - mockDocumentRepository.deleteFile= jest.fn(()=> null) - it('should respond code 200 with confirmation and photo', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) - const payload = { - pseudo: "testPseudo", - alias: "testAlias", - confirmToken: "token", - photo:"path", - bio:"bio" - } - const res = await server.inject({ - method: 'POST', - url: '/users/confirmUser', - payload: payload - }); - expect(res.statusCode).toBe(200); - expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1) - }); - it('should respond code 200 with confirmation', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) - const payload = { - pseudo: "testPseudo", - alias: "testAlias", - confirmToken: "token", - bio:"bio" - } - const res = await server.inject({ - method: 'POST', - url: '/users/confirmUser', - payload: payload - }); - expect(res.statusCode).toBe(200); - expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0) - }); - it('should respond code 400', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn((email,pseudo)=> null) - mockUserRepository.getByConfirmToken = jest.fn(()=>null) - const payload = { - pseudo: "testPseudo", - alias: "testAlias", - confirmToken: "token", - bio:"bio" - } - const res = await server.inject({ - method: 'POST', - url: '/users/confirmUser', - payload: payload - }); - expect(res.statusCode).toBe(400); - }); - it('should respond code 403', async () => { - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>'someting') - const payload = { - pseudo: "testPseudo", - alias: "testAlias", - confirmToken: "token", - bio:"bio" - } - const res = await server.inject({ - method: 'POST', - url: '/users/confirmUser', - payload: payload - }); - expect(res.statusCode).toBe(403); - }); - }) - describe("/users/signin", ()=> { - afterEach(()=>{ - jest.clearAllMocks(); - }) - mockUserRepository.getByUser = jest.fn((test) => { - return 1 - }) - mockAccesTokenManager.generate = ((test) =>{return ''}) - it('should respond code 200', async () => { - const password = 'password' - const mockUserRaw = { - id_utilisateur:"id", - pseudo:"pseudo", - email:"email", - alias:"alias", - bio:"bio", - photo:"path/to/file", - photo_temporaire:"path/to/file", - password:await bcrypt.hash(password,10), - token:"token", - id_role:1, - ban_until:new Date("10-06-2003"), - } - const fetchedUser = new User(mockUserRaw) - - mockUserRepository.getByIdent = jest.fn((ident) =>{ - return fetchedUser - }) - - const res = await server.inject({ - method: 'POST', - url: '/users/signin', - payload: { - email:"tesddesqt@gmaiL.com", - password: password, - }} - ) - - expect(res.statusCode).toBe(200); - - }) - it('should respond code 401 bad password', async () => { - const password = 'password' - const mockUserRaw = { - id_utilisateur:"id", - pseudo:"pseudo", - email:"email", - alias:"alias", - bio:"bio", - photo:"path/to/file", - photo_temporaire:"path/to/file", - password:await bcrypt.hash(password,10), - token:"token", - id_role:1, - ban_until:new Date("10-06-2003"), - } - const fetchedUser = new User(mockUserRaw) - - mockUserRepository.getByIdent = jest.fn((ident) =>{ - return fetchedUser - }) - const res = await server.inject({ - method: 'POST', - url: '/users/signin', - payload: { - email:"tesddesqt@gmaiL.com", - password: "aaaaadaaaaaa", - }} - ) - expect(res.statusCode).toBe(401); - }) - it('should respond code 400 user not found', async () => { - mockUserRepository.getByIdent = jest.fn((ident) =>{ - return null - }) - const res = await server.inject({ - method: 'POST', - url: '/users/signin', - payload: { - email:"fezfez", - password: "aaaaadaaaaaa", - }} - ) - expect(res.statusCode).toBe(401); - }) - it('should respond code 400 invalid email', async () => { - - const res = await server.inject({ - method: 'POST', - url: '/users/signin', - payload: { - email:"fezfez", - password: "a".repeat(31), - }} - ) - expect(res.statusCode).toBe(400); - }) - it('should respond code 400 invalid password', async () => { - - const res = await server.inject({ - method: 'POST', - url: '/users/signin', - payload: { - email:"a".repeat(41), - password: "dzadzaa", - }} - ) - expect(res.statusCode).toBe(400); - }) - }) - describe("/users/isUser", ()=> { - afterEach(()=>{ - jest.clearAllMocks(); - }) - it('should return true', async ()=>{ - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{ - return {id_utilisateur:1} - }) - const res = await server.inject({ - method: 'GET', - url: '/users/isUser?pseudo=test' - }) - expect(res.statusCode).toBe(200); - expect(res.result.isUser).toBe(true) - }) - it('should return false', async ()=>{ - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{ - return null - }) - const res = await server.inject({ - method: 'GET', - url: '/users/isUser?pseudo=test' - }) - expect(res.statusCode).toBe(200); - expect(res.result.isUser).toBe(false) - }) - }) - describe("/users/getUserByConfirmToken", ()=> { - it('should return valid code 200', async ()=>{ - mockUserRepository.getByConfirmToken = jest.fn(()=>{ - return {id_utilisateur:1} - }) - const res = await server.inject({ - method: 'GET', - url: '/users/getUserByConfirmToken?confirmToken=test' - }) - expect(res.statusCode).toBe(200); - }) - it('should return invalid code 403', async ()=>{ - mockUserRepository.getByConfirmToken = jest.fn(()=>{ - return null - }) - const res = await server.inject({ - method: 'GET', - url: '/users/getUserByConfirmToken?confirmToken=test' - }) - expect(res.statusCode).toBe(403); - }) - }) - describe("/users/sendResetEmail", ()=>{ - it("should return valid code 200",async ()=>{ - mockMailRepository.send = jest.fn(()=>{}) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed: true}}) - mockUserRepository.updateUser = jest.fn(()=>{}) - const res = await server.inject({ - method: 'POST', - url: '/users/sendResetEmail', - payload: { - email:"chanon.mael@gmail.com", - }} - ) - expect(res.statusCode).toBe(200); - expect(mockMailRepository.send).toHaveBeenCalledTimes(1) - }) - it("should return valid code 200 even though the user is not confirmed",async ()=>{ - mockMailRepository.send = jest.fn(()=>{}) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return {reset_token: 1,confirmed:false}}) - mockUserRepository.updateUser = jest.fn(()=>{}) - const res = await server.inject({ - method: 'POST', - url: '/users/sendResetEmail', - payload: { - email:"chanon.mael@gmail.com", - }} - ) - expect(res.statusCode).toBe(200); - expect(mockMailRepository.send).toHaveBeenCalledTimes(0) - }) - it("should return valid code 200 even though the user doesn't exists",async ()=>{ - mockMailRepository.send = jest.fn(()=>{}) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>{return null}) - mockUserRepository.updateUser = jest.fn(()=>{}) - const res = await server.inject({ - method: 'POST', - url: '/users/sendResetEmail', - payload: { - email:"chanon.mael@gmail.com", - }} - ) - expect(res.statusCode).toBe(200); - expect(mockMailRepository.send).toHaveBeenCalledTimes(0) - }) - }) - describe("/users/resetPassword", ()=>{ - it("should return valid code 200",async ()=>{ - mockUserRepository.getByResetToken = jest.fn(()=>{return {id:1}}) - mockUserRepository.updateUser = jest.fn(()=>{}) - const res = await server.inject({ - method: 'POST', - url: '/users/resetPassword', - payload: { - resetToken: "eztgergrehre", - password:"TestPassword", - }} - ) - expect(res.statusCode).toBe(200); - }) - it("should return valid code 400 error on token",async ()=>{ - mockUserRepository.getByResetToken = jest.fn(()=> null) - mockUserRepository.updateUser = jest.fn(()=>{}) - const res = await server.inject({ - method: 'POST', - url: '/users/resetPassword', - payload: { - resetToken: "eztgergrehre", - password:"TestPassword", - }} - ) - expect(res.statusCode).toBe(400); - }) - }) - describe("/users/follow", ()=>{ - it("should return valid code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => "something") - mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>"something") - mockFollowRepository.doesFollows = jest.fn(()=> true) - mockFollowRepository.follow = jest.fn(()=> {}) - mockFollowRepository.unfollow = jest.fn(()=> {}) - const res = await server.inject({ - method: 'POST', - url: '/users/follow', - payload: { - artistId: "eztgergrehre", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(200); - }) - it("should return error code 401",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => null) - - const res = await server.inject({ - method: 'POST', - url: '/users/follow', - payload: { - artistId: "eztgergrehre", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(401); - }) - it("should return return invalid code 415",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => "something") - mockSpotifyRepository.getSpotifyArtist = jest.fn(()=>{ - return { - error: { - status:415, - message: "message" - } - } - }) - const res = await server.inject({ - method: 'POST', - url: '/users/follow', - payload: { - artistId: "eztgergrehre", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(415); - }) - }) - describe('AuthWithSpotifyTest', () =>{ - const mockSpotifyCode = 'code' - const email = "some@mail" - const display_name = "name" - const access_token = 'access_token' - const refresh_token = 'refresh_token' - const images = ["https://i.ytimg.com/vi/uLHdmBf1lvs/hq720.jpg?sqp=-oaymwEXCNAFEJQDSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAmH-kUIb43CviOetK-ZjGl0AnSog"] - beforeEach(() => { - mockUserRepository.updateUser = jest.fn(() => "ok") - }) - it("should throw error 400", async ()=>{ - mockSpotifyRepository.getToken = jest.fn(()=> { - return {error: 'some error'} - }) - const res = await server.inject({ - method: 'POST', - url: '/users/authWithSpotify', - payload: { - spotify_code: "eztgergrehre", - }, - }) - expect(res.statusCode).toBe(400) - - }) - it("should throw error 403 1", async ()=>{ - mockSpotifyRepository.getToken = jest.fn(()=> { - return { - access_token, - refresh_token - } - }) - mockSpotifyRepository.getAccountData = jest.fn(()=> { - return {email,display_name,images} - }) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { - return { - confirmed: false - } - }) - const res = await server.inject({ - method: 'POST', - url: '/users/authWithSpotify', - payload: { - spotify_code: "eztgergrehre", - callback: "callback" - }, - }) - expect(res.statusCode).toBe(403) - }) - it("should throw error 403 2", async ()=>{ - mockSpotifyRepository.getToken = jest.fn(()=> { - return { - access_token, - refresh_token - } - }) - mockSpotifyRepository.getAccountData = jest.fn(()=> { - return {email,display_name,images} - }) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { - return { - confirmed: true, - } - }) - mockAccesTokenManager.generate = jest.fn(() => 'expected_token') - const res = await server.inject({ - method: 'POST', - url: '/users/authWithSpotify', - payload: { - spotify_code: "eztgergrehre", - callback: "callback" - }, - }) - expect(res.statusCode).toBe(403) - }) - it("should return auth token", async ()=>{ - mockSpotifyRepository.getToken = jest.fn(()=> { - return { - access_token, - refresh_token - } - }) - mockSpotifyRepository.getAccountData = jest.fn(()=> { - return {email,display_name,images} - }) - mockUserRepository.getByEmailOrPseudo = jest.fn(()=> { - return { - confirmed: true, - refresh_token: 'someting' - } - }) - mockAccesTokenManager.generate = jest.fn(() => 'expected_token') - const res = await server.inject({ - method: 'POST', - url: '/users/authWithSpotify', - payload: { - spotify_code: "eztgergrehre", - callback: "callback" - }, - }) - expect(res.statusCode).toBe(200) - - }) - }) - describe("/users/status", ()=>{ - mockAccesTokenManager.decode = jest.fn((token)=> {return {value: 1}}) - mockUserRepository.changePrivateStatus = jest.fn((id)=>{ - return new User({ - id_utilisateur : 1, - pseudo : "pseudo", - email : "test@test.fr", - password : "hjkklllllm", - id_role : 1, - - }) - }); - it("should return valid code 200",async ()=>{ - const res = await server.inject({ - method: 'POST', - url: '/users/status', - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(200); - expect(mockUserRepository.changePrivateStatus).toHaveBeenCalledTimes(1); - }) - }) - - describe("/users/oeuvreFav", ()=> { - afterEach(()=>{ - jest.clearAllMocks(); - }) - - const { albumRawOneArtist } = require("../unit/interfaces/serializers/fixtures/albumFixture.js") - const { rawTrackWithOneArtist } = require("../unit/interfaces/serializers/fixtures/albumTrackFixture.js") - - // login erreur - it("should return error code 401",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => null) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: "test", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(401); - }) - - // payload incorrect avec entity - it("should return error code 400",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: null, - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(400); - }) - - // payload incorrect avec entity - it("should return error code 400",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: 123, // doit être un string - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(400); - }) - // aucune oeuvre de trouve - it("should return error code 404",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return {error : {status: 400, message: "invalid id"}} - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return {error : {status: 400, message: "invalid id"}} - }) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: "test1323", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(404); - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1); - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1); - - }) - - // plus de 3 oeuvres favorites avec album - it("should return error code 403",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return Promise.resolve(albumRawOneArtist); - }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: "test123", // doit être un string - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(403) - }) - - // plus de 3 oeuvres favorites avec track - it("should return error code 403",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return {error : {status: 400, message: "invalid id"}} - }) - - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return Promise.resolve(rawTrackWithOneArtist); - }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) - - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', - payload: { - idOeuvre: "test123", - }, - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(403) - }) + aud: "urn:audience:test", // needs to match definition above + iss: "urn:issuer:test", // needs to match definition above, + expiresIn: "365d", + }, + process.env.SECRET_ENCODER +); + +mockAccesTokenManager.generate = (test) => { + return ""; +}; +describe("user route", () => { + beforeEach(async () => { + server = Hapi.server({ + port: process.env.PORT || 3000, + }); + server.app.serviceLocator = { + userRepository: mockUserRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + mailRepository: mockMailRepository, + documentRepository: mockDocumentRepository, + followRepository: mockFollowRepository, + oeuvreFavRepository: mockOeuvreFavRepository, + }; + server.register(Jwt); + server.auth.strategy( + "jwt", + "jwt", + strategy({ userRepository: mockUserRepository }) + ); + await server.register([require("../../lib/interfaces/routes/users")]); + }); + + afterEach(async () => { + jest.clearAllMocks(); + await server.stop(); + }); + describe("/users/createUser", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + mockUserRepository.getByEmailOrPseudo = jest.fn((email, pseudo) => null); + mockUserRepository.persist = jest.fn((test) => { + return { id_utilisateur: 1 }; + }); + mockMailRepository.send = jest.fn((option) => null); + mockSpotifyRepository.getToken = jest.fn(() => { + return { access_token: 1, refresh_token: 1 }; + }); + it("should respond code 200 with email inscription", async () => { + const res = await server.inject({ + method: "POST", + url: "/users/createUser", + payload: { + email: "tesddesqt@gmaiL.com", + password: "somepassword", + }, + }); + expect(res.statusCode).toBe(200); + }); - // ajout réussie avec album - it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return Promise.resolve(albumRawOneArtist); + it("should respond code 403 with already existing email", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn( + (email, pseudo) => "something" + ); + const res = await server.inject({ + method: "POST", + url: "/users/createUser", + payload: { + email: "tesddesqt@gmaiL.com", + password: "somepassword", + }, + }); + expect(res.statusCode).toBe(403); + }); + }); + describe("/users/confirmUser", () => { + afterEach(() => { + jest.clearAllMocks(); }); - mockSpotifyRepository.getSpotifyTracks = jest.fn(); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', + mockUserRepository.getByConfirmToken = jest.fn(() => { + return { + id_utilisateur: 1, + photo_temporaire: "path", + }; + }); + mockUserRepository.updateUser = jest.fn((user) => user); + mockUserRepository.persist = jest.fn((test) => { + return { id_utilisateur: 1 }; + }); + mockDocumentRepository.deleteFile = jest.fn(() => null); + it("should respond code 200 with confirmation and photo", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email, pseudo) => null); + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + confirmToken: "token", + photo: "path", + bio: "bio", + }; + const res = await server.inject({ + method: "POST", + url: "/users/confirmUser", + payload: payload, + }); + expect(res.statusCode).toBe(200); + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(1); + }); + it("should respond code 200 with confirmation", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email, pseudo) => null); + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + confirmToken: "token", + bio: "bio", + }; + const res = await server.inject({ + method: "POST", + url: "/users/confirmUser", + payload: payload, + }); + expect(res.statusCode).toBe(200); + expect(mockDocumentRepository.deleteFile).toHaveBeenCalledTimes(0); + }); + it("should respond code 400", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn((email, pseudo) => null); + mockUserRepository.getByConfirmToken = jest.fn(() => null); + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + confirmToken: "token", + bio: "bio", + }; + const res = await server.inject({ + method: "POST", + url: "/users/confirmUser", + payload: payload, + }); + expect(res.statusCode).toBe(400); + }); + it("should respond code 403", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(() => "someting"); + const payload = { + pseudo: "testPseudo", + alias: "testAlias", + confirmToken: "token", + bio: "bio", + }; + const res = await server.inject({ + method: "POST", + url: "/users/confirmUser", + payload: payload, + }); + expect(res.statusCode).toBe(403); + }); + }); + describe("/users/signin", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + mockUserRepository.getByUser = jest.fn((test) => { + return 1; + }); + mockAccesTokenManager.generate = (test) => { + return ""; + }; + it("should respond code 200", async () => { + const password = "password"; + const mockUserRaw = { + id_utilisateur: "id", + pseudo: "pseudo", + email: "email", + alias: "alias", + bio: "bio", + photo: "path/to/file", + photo_temporaire: "path/to/file", + password: await bcrypt.hash(password, 10), + token: "token", + id_role: 1, + ban_until: new Date("10-06-2003"), + }; + const fetchedUser = new User(mockUserRaw); + + mockUserRepository.getByIdent = jest.fn((ident) => { + return fetchedUser; + }); + + const res = await server.inject({ + method: "POST", + url: "/users/signin", + payload: { + email: "tesddesqt@gmaiL.com", + password: password, + }, + }); + + expect(res.statusCode).toBe(200); + }); + it("should respond code 401 bad password", async () => { + const password = "password"; + const mockUserRaw = { + id_utilisateur: "id", + pseudo: "pseudo", + email: "email", + alias: "alias", + bio: "bio", + photo: "path/to/file", + photo_temporaire: "path/to/file", + password: await bcrypt.hash(password, 10), + token: "token", + id_role: 1, + ban_until: new Date("10-06-2003"), + }; + const fetchedUser = new User(mockUserRaw); + + mockUserRepository.getByIdent = jest.fn((ident) => { + return fetchedUser; + }); + const res = await server.inject({ + method: "POST", + url: "/users/signin", + payload: { + email: "tesddesqt@gmaiL.com", + password: "aaaaadaaaaaa", + }, + }); + expect(res.statusCode).toBe(401); + }); + it("should respond code 400 user not found", async () => { + mockUserRepository.getByIdent = jest.fn((ident) => { + return null; + }); + const res = await server.inject({ + method: "POST", + url: "/users/signin", + payload: { + email: "fezfez", + password: "aaaaadaaaaaa", + }, + }); + expect(res.statusCode).toBe(401); + }); + it("should respond code 400 invalid email", async () => { + const res = await server.inject({ + method: "POST", + url: "/users/signin", + payload: { + email: "fezfez", + password: "a".repeat(31), + }, + }); + expect(res.statusCode).toBe(400); + }); + it("should respond code 400 invalid password", async () => { + const res = await server.inject({ + method: "POST", + url: "/users/signin", + payload: { + email: "a".repeat(41), + password: "dzadzaa", + }, + }); + expect(res.statusCode).toBe(400); + }); + }); + describe("/users/isUser", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it("should return true", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { id_utilisateur: 1 }; + }); + const res = await server.inject({ + method: "GET", + url: "/users/isUser?pseudo=test", + }); + expect(res.statusCode).toBe(200); + expect(res.result.isUser).toBe(true); + }); + it("should return false", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return null; + }); + const res = await server.inject({ + method: "GET", + url: "/users/isUser?pseudo=test", + }); + expect(res.statusCode).toBe(200); + expect(res.result.isUser).toBe(false); + }); + }); + describe("/users/getUserByConfirmToken", () => { + it("should return valid code 200", async () => { + mockUserRepository.getByConfirmToken = jest.fn(() => { + return { id_utilisateur: 1 }; + }); + const res = await server.inject({ + method: "GET", + url: "/users/getUserByConfirmToken?confirmToken=test", + }); + expect(res.statusCode).toBe(200); + }); + it("should return invalid code 403", async () => { + mockUserRepository.getByConfirmToken = jest.fn(() => { + return null; + }); + const res = await server.inject({ + method: "GET", + url: "/users/getUserByConfirmToken?confirmToken=test", + }); + expect(res.statusCode).toBe(403); + }); + }); + describe("/users/sendResetEmail", () => { + it("should return valid code 200", async () => { + mockMailRepository.send = jest.fn(() => {}); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { reset_token: 1, confirmed: true }; + }); + mockUserRepository.updateUser = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/sendResetEmail", payload: { - idOeuvre: "test123", + email: "chanon.mael@gmail.com", + }, + }); + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(1); + }); + it("should return valid code 200 even though the user is not confirmed", async () => { + mockMailRepository.send = jest.fn(() => {}); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { reset_token: 1, confirmed: false }; + }); + mockUserRepository.updateUser = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/sendResetEmail", + payload: { + email: "chanon.mael@gmail.com", + }, + }); + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(0); + }); + it("should return valid code 200 even though the user doesn't exists", async () => { + mockMailRepository.send = jest.fn(() => {}); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return null; + }); + mockUserRepository.updateUser = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/sendResetEmail", + payload: { + email: "chanon.mael@gmail.com", + }, + }); + expect(res.statusCode).toBe(200); + expect(mockMailRepository.send).toHaveBeenCalledTimes(0); + }); + }); + describe("/users/resetPassword", () => { + it("should return valid code 200", async () => { + mockUserRepository.getByResetToken = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.updateUser = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/resetPassword", + payload: { + resetToken: "eztgergrehre", + password: "TestPassword", + }, + }); + expect(res.statusCode).toBe(200); + }); + it("should return valid code 400 error on token", async () => { + mockUserRepository.getByResetToken = jest.fn(() => null); + mockUserRepository.updateUser = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/resetPassword", + payload: { + resetToken: "eztgergrehre", + password: "TestPassword", + }, + }); + expect(res.statusCode).toBe(400); + }); + }); + describe("/users/follow", () => { + it("should return valid code 200", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => "something"); + mockSpotifyRepository.getSpotifyArtist = jest.fn(() => "something"); + mockFollowRepository.doesFollows = jest.fn(() => true); + mockFollowRepository.follow = jest.fn(() => {}); + mockFollowRepository.unfollow = jest.fn(() => {}); + const res = await server.inject({ + method: "POST", + url: "/users/follow", + payload: { + artistId: "eztgergrehre", }, headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(200) -}) - -// ajout réussie avec track -it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return {error : {status: 400, message: "invalid id"}} - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return Promise.resolve(rawTrackWithOneArtist); + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(200); + }); + it("should return error code 401", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => null); + + const res = await server.inject({ + method: "POST", + url: "/users/follow", + payload: { + artistId: "eztgergrehre", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(401); + }); + it("should return return invalid code 415", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => "something"); + mockSpotifyRepository.getSpotifyArtist = jest.fn(() => { + return { + error: { + status: 415, + message: "message", + }, + }; + }); + const res = await server.inject({ + method: "POST", + url: "/users/follow", + payload: { + artistId: "eztgergrehre", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(415); + }); + }); + describe("AuthWithSpotifyTest", () => { + const mockSpotifyCode = "code"; + const email = "some@mail"; + const display_name = "name"; + const access_token = "access_token"; + const refresh_token = "refresh_token"; + const images = [ + "https://i.ytimg.com/vi/uLHdmBf1lvs/hq720.jpg?sqp=-oaymwEXCNAFEJQDSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAmH-kUIb43CviOetK-ZjGl0AnSog", + ]; + beforeEach(() => { + mockUserRepository.updateUser = jest.fn(() => "ok"); + }); + it("should throw error 400", async () => { + mockSpotifyRepository.getToken = jest.fn(() => { + return { error: "some error" }; + }); + const res = await server.inject({ + method: "POST", + url: "/users/authWithSpotify", + payload: { + spotify_code: "eztgergrehre", + }, + }); + expect(res.statusCode).toBe(400); + }); + it("should throw error 403 1", async () => { + mockSpotifyRepository.getToken = jest.fn(() => { + return { + access_token, + refresh_token, + }; + }); + mockSpotifyRepository.getAccountData = jest.fn(() => { + return { email, display_name, images }; + }); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { + confirmed: false, + }; + }); + const res = await server.inject({ + method: "POST", + url: "/users/authWithSpotify", + payload: { + spotify_code: "eztgergrehre", + callback: "callback", + }, + }); + expect(res.statusCode).toBe(403); + }); + it("should throw error 403 2", async () => { + mockSpotifyRepository.getToken = jest.fn(() => { + return { + access_token, + refresh_token, + }; + }); + mockSpotifyRepository.getAccountData = jest.fn(() => { + return { email, display_name, images }; + }); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { + confirmed: true, + }; + }); + mockAccesTokenManager.generate = jest.fn(() => "expected_token"); + const res = await server.inject({ + method: "POST", + url: "/users/authWithSpotify", + payload: { + spotify_code: "eztgergrehre", + callback: "callback", + }, + }); + expect(res.statusCode).toBe(403); + }); + it("should return auth token", async () => { + mockSpotifyRepository.getToken = jest.fn(() => { + return { + access_token, + refresh_token, + }; + }); + mockSpotifyRepository.getAccountData = jest.fn(() => { + return { email, display_name, images }; + }); + mockUserRepository.getByEmailOrPseudo = jest.fn(() => { + return { + confirmed: true, + refresh_token: "someting", + }; + }); + mockAccesTokenManager.generate = jest.fn(() => "expected_token"); + const res = await server.inject({ + method: "POST", + url: "/users/authWithSpotify", + payload: { + spotify_code: "eztgergrehre", + callback: "callback", + }, + }); + expect(res.statusCode).toBe(200); + }); + }); + describe("/users/status", () => { + mockAccesTokenManager.decode = jest.fn((token) => { + return { value: 1 }; }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.addOeuvrefav = jest.fn() + mockUserRepository.changePrivateStatus = jest.fn((id) => { + return new User({ + id_utilisateur: 1, + pseudo: "pseudo", + email: "test@test.fr", + password: "hjkklllllm", + id_role: 1, + }); + }); + it("should return valid code 200", async () => { + const res = await server.inject({ + method: "POST", + url: "/users/status", + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(200); + expect(mockUserRepository.changePrivateStatus).toHaveBeenCalledTimes(1); + }); + }); - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', + describe("/users/oeuvreFav", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const { + albumRawOneArtist, + } = require("../unit/interfaces/serializers/fixtures/albumFixture.js"); + const { + rawTrackWithOneArtist, + } = require("../unit/interfaces/serializers/fixtures/albumTrackFixture.js"); + + // login erreur + it("should return error code 401", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => null); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(401); + }); + + // payload incorrect avec entity + it("should return error code 400", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: null, + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(400); + }); + + // payload incorrect avec entity + it("should return error code 400", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", payload: { - idOeuvre: "test123", + idOeuvre: 123, // doit être un string }, headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(200) -}) - - // supression reussite avec album - it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(400); + }); + // aucune oeuvre de trouve + it("should return error code 404", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return { error: { status: 404, message: "invalid id" } }; + }); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test1323", + type: "track", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(404); + expect(mockSpotifyRepository.getOeuvre).toHaveBeenCalledTimes(1); + }); + + // plus de 3 oeuvres favorites avec album + it("should return error code 403 1", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { return Promise.resolve(albumRawOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test123", // doit être un string + type: "album", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(res.statusCode).toBe(403); }); - - mockSpotifyRepository.getSpotifyTracks = jest.fn(); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.ajoutPossible = jest.fn() - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() + // plus de 3 oeuvres favorites avec track + it("should return error code 403 2", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test123", + type: "track", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + + // expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1); + // expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(res.statusCode).toBe(403); + }); + + // ajout réussie avec album + it("should return code 200 1", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", payload: { - idOeuvre: "test123", + idOeuvre: "test123", + type: "track", }, headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); - expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(200) -}) - - // supression reussite avec track -it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return {error : {status: 400, message: "invalid id"}} - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { + Authorization: `Bearer ${mockToken}`, + }, + }); + + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1); + expect(res.statusCode).toBe(200); + }); + + // ajout réussie avec track + it("should return code 200 2", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test123", + type: "track", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1); + expect(res.statusCode).toBe(200); }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.ajoutPossible = jest.fn() - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() - const res = await server.inject({ - method: 'POST', - url: '/users/oeuvreFav', + // supression reussite avec album + it("should return code 200 3 ", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + + mockSpotifyRepository.getSpotifyTracks = jest.fn(); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn(); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn(); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", payload: { - idOeuvre: "test123", + idOeuvre: "test123", + type: "track", }, headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled() - expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) - expect(res.statusCode).toBe(200) - }) - -}) - - - describe("/users/getOeuvresFav", ()=> { - afterEach(()=>{ - jest.clearAllMocks(); - }) - it("should return error code 401",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => null) - - const res = await server.inject({ - method: 'GET', - url: '/users/getOeuvresFav', - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(401); - }) - }) - it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { - return [1,2,3] - }) - const res = await server.inject({ - method: 'GET', - url: '/users/getOeuvresFav', - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(200); - }) - - it("should return code 200",async ()=>{ - mockAccesTokenManager.decode = jest.fn(()=>{return {id:1}}) - mockUserRepository.getByUser = jest.fn(() => 1) - mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { - return [] - }) - const res = await server.inject({ - method: 'GET', - url: '/users/getOeuvresFav', - headers: { - Authorization: `Bearer ${mockToken}` - } - }) - expect(res.statusCode).toBe(200); - }) + Authorization: `Bearer ${mockToken}`, + }, + }); -}); + expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(200); + }); - + // supression reussite avec track + it("should return code 200 4", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn(); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn(); + + const res = await server.inject({ + method: "POST", + url: "/users/oeuvreFav", + payload: { + idOeuvre: "test123", + type: "track", + }, + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(res.statusCode).toBe(200); + }); + }); + + describe("/users/getOeuvresFav", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it("should return error code 401", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => null); + + const res = await server.inject({ + method: "GET", + url: "/users/getOeuvresFav", + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(401); + }); + }); + it("should return code 200", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { + return [1, 2, 3]; + }); + const res = await server.inject({ + method: "GET", + url: "/users/getOeuvresFav", + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(200); + }); + it("should return code 200 ", async () => { + mockAccesTokenManager.decode = jest.fn(() => { + return { id: 1 }; + }); + mockUserRepository.getByUser = jest.fn(() => 1); + mockOeuvreFavRepository.getOeuvresFav = jest.fn((id) => { + return []; + }); + const res = await server.inject({ + method: "GET", + url: "/users/getOeuvresFav", + headers: { + Authorization: `Bearer ${mockToken}`, + }, + }); + expect(res.statusCode).toBe(200); + }); +}); diff --git a/test/unit/application/usecase/artist/getArtistFixture.js b/test/unit/application/usecase/artist/getArtistFixture.js index a41748b..4f2c7b2 100644 --- a/test/unit/application/usecase/artist/getArtistFixture.js +++ b/test/unit/application/usecase/artist/getArtistFixture.js @@ -1,387 +1,398 @@ const mockArtist = { - external_urls: { spotify: 'https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN' }, - genres: [ 'french hip hop', 'old school rap francais', 'rap conscient' ], - id: '4FpJcNgOvIpSBeJgRg3OfN', - images: [ - { - height: 640, - url: 'https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde', - width: 640 - }, - ], - name: 'Orelsan', - popularity: 64, - type: 'artist', - } + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + }, + genres: ["french hip hop", "old school rap francais", "rap conscient"], + id: "4FpJcNgOvIpSBeJgRg3OfN", + images: [ + { + height: 640, + url: "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", + width: 640, + }, + ], + name: "Orelsan", + popularity: 64, + type: "artist", +}; const mockAlbumRaw = { - "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", - "items": [ - { - "album_group": "album", - "album_type": "album", - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" - }, - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist" - } - ], - "external_urls": { - "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + href: "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN/albums?include_groups=album,single&offset=0&limit=10", + items: [ + { + album_group: "album", + album_type: "album", + artists: [ + { + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + }, + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", }, - "id": "68YP0pEgwhnfRqQAzu71gP", - "images": [ - { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "width": 640 - } - ], - "name": "Civilisation Edition Ultime", - "release_date": "2022-10-28", - "total_tracks": 25, - "type": "album" - } - ] -} + ], + external_urls: { + spotify: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + }, + id: "68YP0pEgwhnfRqQAzu71gP", + images: [ + { + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + width: 640, + }, + ], + name: "Civilisation Edition Ultime", + release_date: "2022-10-28", + total_tracks: 25, + type: "album", + }, + ], +}; const mockUser = { - id_utilisateur: 1, - pseudo: "John Doe", - alias: "John", - ban_until: null, - email: "testemail@gmail", - id_role: 1, - photo: null, - photo_temporaire: null, - type: "user", - is_private: false -} + id_utilisateur: 1, + pseudo: "John Doe", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: false, +}; const mockUserPrivate = { - pseudo: "John Doe2", - alias: "John", - ban_until: null, - email: "testemail@gmail", - id_role: 1, - id_utilisateur: 1, - photo: null, - photo_temporaire: null, - type: "user", - is_private: true -} -const actualDate = new Date() - + pseudo: "John Doe2", + alias: "John", + ban_until: null, + email: "testemail@gmail", + id_role: 1, + id_utilisateur: 1, + photo: null, + photo_temporaire: null, + type: "user", + is_private: true, +}; +const actualDate = new Date(); const mockLikedReview = { - "id_review": 25, - "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlike": 32, - "countComment": 0, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "note": 5, - "createdAt": "2024-01-03T00:00:00.000Z", - "updated_at": "2024-01-03T00:00:00.000Z", - "type": "album", - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "token": null, - "refresh_token": null, - "reset_token": null, - "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", - "id_role": 1, - "ban_until": null, - "confirmed": true, - "confirm_token": null, - "auth_with_spotify": false, - "is_private": true, - "type": "user" - } -} + id_review: 25, + id_oeuvre: "68YP0pEgwhnfRqQAzu71gP", + countlikes: 32, + countComment: 0, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + note: 5, + createdAt: "2024-01-03T00:00:00.000Z", + updated_at: "2024-01-03T00:00:00.000Z", + type: "album", + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + email: "Constance.Constance@gmail.com", + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + token: null, + refresh_token: null, + reset_token: null, + password: "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + id_role: 1, + ban_until: null, + confirmed: true, + confirm_token: null, + auth_with_spotify: false, + is_private: true, + type: "user", + }, +}; const mockCommentedReview = { - "id_review": 25, - "id_oeuvre": "68YP0pEgwhnfRqQAzu71gP", - "countlike": 32, - "countComment": 0, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "note": 5, - "createdAt": "2024-01-03T00:00:00.000Z", - "updated_at": "2024-01-03T00:00:00.000Z", - "type": "album", - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "token": null, - "refresh_token": null, - "reset_token": null, - "password": "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", - "id_role": 1, - "ban_until": null, - "confirmed": true, - "confirm_token": null, - "auth_with_spotify": false, - "is_private": true, - "type": "user" - } -} + id_review: 25, + id_oeuvre: "68YP0pEgwhnfRqQAzu71gP", + countlikes: 32, + countComment: 0, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + note: 5, + createdAt: "2024-01-03T00:00:00.000Z", + updated_at: "2024-01-03T00:00:00.000Z", + type: "album", + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + email: "Constance.Constance@gmail.com", + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + token: null, + refresh_token: null, + reset_token: null, + password: "$2b$10$feqJS.qjWmoYovS805Mmhu.7VjOncR68em0Bu4LSxS/4tDSP8HWK2", + id_role: 1, + ban_until: null, + confirmed: true, + confirm_token: null, + auth_with_spotify: false, + is_private: true, + type: "user", + }, +}; const mockOeuvreReviewSpotify = { - "album_type": "album", - "artists": [ + album_type: "album", + artists: [ { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", }, - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", + }, ], - "external_urls": { - "spotify": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP" + external_urls: { + spotify: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", }, - "genres": [], - "id": "68YP0pEgwhnfRqQAzu71gP", - "images": [ + genres: [], + id: "68YP0pEgwhnfRqQAzu71gP", + images: [ { - "height": 640, - "url": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "width": 640 - } + height: 640, + url: "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + width: 640, + }, ], - "name": "Civilisation Edition Ultime", - "popularity": 60, - "release_date": "2022-10-28", - "total_tracks": 1, - "tracks": { - "href": "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", - "items": [ - { - "artists": [ - { - "external_urls": { - "spotify": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN" - }, - "href": "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "type": "artist", - "uri": "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN" - } - ], - - "disc_number": 1, - "duration_ms": 161732, - "external_urls": { - "spotify": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4" + name: "Civilisation Edition Ultime", + popularity: 60, + release_date: "2022-10-28", + total_tracks: 1, + tracks: { + href: "https://api.spotify.com/v1/albums/68YP0pEgwhnfRqQAzu71gP/tracks?offset=0&limit=50", + items: [ + { + artists: [ + { + external_urls: { + spotify: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", }, - "id": "7b3YQboXo3kau9YVUyx3r4", - "name": "CP_001_ Intro Civilisation Perdue", - "preview_url": "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", - "track_number": 1, - "type": "track" - } + href: "https://api.spotify.com/v1/artists/4FpJcNgOvIpSBeJgRg3OfN", + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + type: "artist", + uri: "spotify:artist:4FpJcNgOvIpSBeJgRg3OfN", + }, + ], + + disc_number: 1, + duration_ms: 161732, + external_urls: { + spotify: "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + }, + id: "7b3YQboXo3kau9YVUyx3r4", + name: "CP_001_ Intro Civilisation Perdue", + preview_url: + "https://p.scdn.co/mp3-preview/b7f3ef4f7ee54bcba700a821952539b7e4e21d2f?cid=24b7c8fbb7d146edbce14e1743cea479", + track_number: 1, + type: "track", + }, ], - "total": 25 + total: 25, }, - "type": "album" -} - + type: "album", +}; const expectedArtist = { - "artist": { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", - "popularity": 64, - "follower_count": 100, - "genres": [ - "french hip hop", - "old school rap francais", - "rap conscient" - ], - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" + artist: { + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: "https://i.scdn.co/image/ab6761610000e5eb32086a424e6f1e499e347cde", + popularity: 64, + follower_count: 100, + genres: ["french hip hop", "old school rap francais", "rap conscient"], + spotify_url: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", }, - "albums": [ + albums: [ { - "id": "68YP0pEgwhnfRqQAzu71gP", - "name": "Civilisation Edition Ultime", - "popularity": 64, - "rating": 1, - "reviewCount": 2, - "release_date": "2022-10-28", - "total_tracks": 25, - "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", - "artists": [ + id: "68YP0pEgwhnfRqQAzu71gP", + name: "Civilisation Edition Ultime", + popularity: 64, + rating: 1, + reviewCount: 2, + release_date: "2022-10-28", + total_tracks: 25, + image: "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + spotify_url: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + artists: [ { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": null, - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: null, + spotify_url: "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", + }, ], - "type": "album" - } + type: "album", + }, ], - "friends_followers": { - "count": 1, - "users": [ + friends_followers: { + count: 1, + users: [ { - "id_utilisateur": 1, - "pseudo": "John Doe2", - "email": "testemail@gmail", - "alias": "John", - "photo": null, - "photo_temporaire": null, - "id_role": 1, - "ban_until": null, - "is_private": true, - "type": "user" - } - ] + id_utilisateur: 1, + pseudo: "John Doe2", + email: "testemail@gmail", + alias: "John", + photo: null, + photo_temporaire: null, + id_role: 1, + ban_until: null, + is_private: true, + type: "user", + }, + ], }, - "reviewsByLike": [ + reviewsByLike: [ { - "id_review": 25, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "countlike": 32, - "countComment": 0, - "doesUserLike": true, - "createdAt": "2024-01-03T00:00:00.000Z", - "note": 5, - "oeuvre": { - "id": "68YP0pEgwhnfRqQAzu71gP", - "name": "Civilisation Edition Ultime", - "popularity": 60, - "release_date": "2022-10-28", - "total_tracks": 1, - "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", - "artists": [ + id_review: 25, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + countlikes: 32, + countComment: 0, + doesUserLike: true, + createdAt: "2024-01-03T00:00:00.000Z", + note: 5, + oeuvre: { + id: "68YP0pEgwhnfRqQAzu71gP", + name: "Civilisation Edition Ultime", + popularity: 60, + release_date: "2022-10-28", + total_tracks: 1, + image: + "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + spotify_url: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + artists: [ { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": null, - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: null, + spotify_url: + "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", + }, ], - "tracks": [ + tracks: [ { - "id": "7b3YQboXo3kau9YVUyx3r4", - "name": "CP_001_ Intro Civilisation Perdue", - "artists": [ + id: "7b3YQboXo3kau9YVUyx3r4", + name: "CP_001_ Intro Civilisation Perdue", + artists: [ { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": null, - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: null, + spotify_url: + "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", + }, ], - "duration_ms": 161732, - "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", - "type": "track" - } + duration_ms: 161732, + spotify_url: + "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + type: "track", + }, ], - "genres": [], - "type": "album" + genres: [], + type: "album", }, - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "id_role": 1, - "ban_until": null, - "is_private": true, - "type": "user" + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + auth_with_spotify: false, + email: "Constance.Constance@gmail.com", + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + id_role: 1, + ban_until: null, + is_private: true, + type: "user", }, - "type": "album" - } + type: "album", + }, ], - "reviewsByTime": [ + reviewsByTime: [ { - "id_review": 25, - "description": "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", - "countlike": 32, - "createdAt": "2024-01-03T00:00:00.000Z", - "countComment": 0, - "doesUserLike": true, - "note": 5, - "oeuvre": { - "id": "68YP0pEgwhnfRqQAzu71gP", - "name": "Civilisation Edition Ultime", - "popularity": 60, - "release_date": "2022-10-28", - "total_tracks": 1, - "image": "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", - "spotify_url": "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", - "artists": [ + id_review: 25, + description: + "Innovation sonore à son apogée, repoussant les limites de l'expérimentation musicale. #AvantGarde #SoundExploration", + countlikes: 32, + createdAt: "2024-01-03T00:00:00.000Z", + countComment: 0, + doesUserLike: true, + note: 5, + oeuvre: { + id: "68YP0pEgwhnfRqQAzu71gP", + name: "Civilisation Edition Ultime", + popularity: 60, + release_date: "2022-10-28", + total_tracks: 1, + image: + "https://i.scdn.co/image/ab67616d0000b2732724364cd86bb791926b6cc8", + spotify_url: "https://open.spotify.com/album/68YP0pEgwhnfRqQAzu71gP", + artists: [ { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": null, - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: null, + spotify_url: + "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", + }, ], - "tracks": [ + tracks: [ { - "id": "7b3YQboXo3kau9YVUyx3r4", - "name": "CP_001_ Intro Civilisation Perdue", - "artists": [ + id: "7b3YQboXo3kau9YVUyx3r4", + name: "CP_001_ Intro Civilisation Perdue", + artists: [ { - "id": "4FpJcNgOvIpSBeJgRg3OfN", - "name": "Orelsan", - "image": null, - "spotify_url": "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", - "type": "artist" - } + id: "4FpJcNgOvIpSBeJgRg3OfN", + name: "Orelsan", + image: null, + spotify_url: + "https://open.spotify.com/artist/4FpJcNgOvIpSBeJgRg3OfN", + type: "artist", + }, ], - "duration_ms": 161732, - "spotify_url": "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", - "type": "track" - } + duration_ms: 161732, + spotify_url: + "https://open.spotify.com/track/7b3YQboXo3kau9YVUyx3r4", + type: "track", + }, ], - "genres": [], - "type": "album" + genres: [], + type: "album", }, - "utilisateur": { - "id_utilisateur": 54, - "pseudo": "Constance", - "email": "Constance.Constance@gmail.com", - "alias": "Constance", - "photo": "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", - "photo_temporaire": null, - "id_role": 1, - "ban_until": null, - "is_private": true, - "type": "user" + utilisateur: { + id_utilisateur: 54, + pseudo: "Constance", + auth_with_spotify: false, + email: "Constance.Constance@gmail.com", + alias: "Constance", + photo: "https://ozgrozer.github.io/100k-faces/0/6/006026.jpg", + photo_temporaire: null, + id_role: 1, + ban_until: null, + is_private: true, + type: "user", }, - "type": "album" - } + type: "album", + }, ], - "doesUserFollow": true -} + doesUserFollow: true, +}; module.exports = { mockUser, mockArtist, @@ -390,5 +401,5 @@ module.exports = { mockLikedReview, mockCommentedReview, mockOeuvreReviewSpotify, - expectedArtist -} \ No newline at end of file + expectedArtist, +}; diff --git a/test/unit/application/usecase/user/getAccessToken.test.js b/test/unit/application/usecase/user/getAccessToken.test.js index 62a8835..6fbe806 100644 --- a/test/unit/application/usecase/user/getAccessToken.test.js +++ b/test/unit/application/usecase/user/getAccessToken.test.js @@ -1,107 +1,93 @@ -const UserRepository = require('../../../../../lib/infrastructure/repositories/interfaces/UserRepositoryAbstract'); -const getAccessToken = require('../../../../../lib/application/use_cases/security/GetAccessToken') +const UserRepository = require("../../../../../lib/infrastructure/repositories/interfaces/UserRepositoryAbstract"); +const getAccessToken = require("../../../../../lib/application/use_cases/security/GetAccessToken"); const bcrypt = require("bcrypt"); -const catchError = require("../utils/catchError") +const catchError = require("../utils/catchError"); +require("dotenv").config(); +describe("getAccessToken", () => { + it("should generate access token", async () => { + const passwordTest = "passwordTest"; + const persistedUserCrypted = { + id_utilisateur: "id", + pseudo: "pseudo", + email: "email", + alias: "alias", + bio: "bio", + photo: "path/to/file", + photo_temporaire: "path/to/file", + password: await bcrypt.hash(passwordTest, 10), + token: "token", + is_private: false, + id_role: 1, + ban_until: new Date("10-06-2003"), + }; + const expectedAccessToken = { + user: { + id_utilisateur: "id", + pseudo: "pseudo", + email: "email", + alias: "alias", + bio: "bio", + photo: `${process.env.API_URL}/path/to/file`, + photo_temporaire: `${process.env.API_URL}/path/to/file`, + type: "user", + is_private: false, + id_role: 1, + ban_until: new Date("10-06-2003"), + }, + token: 1, + }; + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockAccessTokenManager.generate = jest.fn((uid) => 1); + mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted); + expect( + await getAccessToken(persistedUserCrypted.pseudo, passwordTest, { + userRepository: mockUserRepository, + accessTokenManager: mockAccessTokenManager, + }) + ).toEqual(expectedAccessToken); + }); + it("should throw an error because the password is incorrect", async () => { + const passwordTest = "passwordTest"; + const persistedUserCrypted = { + id_utilisateur: "id", + pseudo: "pseudo", + email: "email", + alias: "alias", + bio: "bio", + photo: "path/to/file", + photo_temporaire: "path/to/file", + password: await bcrypt.hash(passwordTest, 10), + token: "token", + id_role: 1, + ban_until: new Date("10-06-2003"), + }; + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted); + const error = await catchError(async () => { + await getAccessToken(persistedUserCrypted.pseudo, "badPassword", { + userRepository: mockUserRepository, + accessTokenManager: mockAccessTokenManager, + }); + }); -describe('getAccessToken', () =>{ - it('should generate access token', async () =>{ - const passwordTest = 'passwordTest' - const persistedUserCrypted = { - id_utilisateur:"id", - pseudo:"pseudo", - email:"email", - alias:"alias", - bio:"bio", - photo:"path/to/file", - photo_temporaire:"path/to/file", - password:await bcrypt.hash(passwordTest,10), - token:"token", - is_private: false, - id_role:1, - ban_until:new Date("10-06-2003"), - } - const expectedAccessToken = { - user: { - id_utilisateur:"id", - pseudo:"pseudo", - email:"email", - alias:"alias", - bio:"bio", - photo:"path/to/file", - photo_temporaire:"path/to/file", - type: 'user', - is_private: false, - id_role:1, - ban_until:new Date("10-06-2003"), - }, - token: 1 - } - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockAccessTokenManager.generate = jest.fn((uid) => 1) - mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) - expect( - await getAccessToken( - persistedUserCrypted.pseudo, - passwordTest, - { - userRepository : mockUserRepository, - accessTokenManager: mockAccessTokenManager - } - ) - ).toEqual(expectedAccessToken) - }) + expect(error.code).toBe(401); + }); - - it('should throw an error because the password is incorrect', async () =>{ - const passwordTest = 'passwordTest' - const persistedUserCrypted = { - id_utilisateur:"id", - pseudo:"pseudo", - email:"email", - alias:"alias", - bio:"bio", - photo:"path/to/file", - photo_temporaire:"path/to/file", - password:await bcrypt.hash(passwordTest,10), - token:"token", - id_role:1, - ban_until:new Date("10-06-2003"), - } - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockUserRepository.getByIdent = jest.fn((ident) => persistedUserCrypted) - const error = await catchError(async ()=>{ - await getAccessToken( - persistedUserCrypted.pseudo, - 'badPassword', - { - userRepository : mockUserRepository, - accessTokenManager: mockAccessTokenManager - } - ) - }) - - expect(error.code).toBe(401) - }) - - it('should throw an error because the user doesnt exists', async () =>{ - const mockUserRepository = new UserRepository(); - const mockAccessTokenManager = {}; - mockUserRepository.getByIdent = jest.fn((ident) => null) - const error = await catchError(async ()=> { - await getAccessToken( - '', - '', - { - userRepository: mockUserRepository, - accessTokenManager: mockAccessTokenManager - }) - } - ) - console.log(error) - expect(error.code).toBe(401) - }) -}) \ No newline at end of file + it("should throw an error because the user doesnt exists", async () => { + const mockUserRepository = new UserRepository(); + const mockAccessTokenManager = {}; + mockUserRepository.getByIdent = jest.fn((ident) => null); + const error = await catchError(async () => { + await getAccessToken("", "", { + userRepository: mockUserRepository, + accessTokenManager: mockAccessTokenManager, + }); + }); + console.log(error); + expect(error.code).toBe(401); + }); +}); diff --git a/test/unit/application/usecase/user/getUserByConfirmToken.test.js b/test/unit/application/usecase/user/getUserByConfirmToken.test.js index 2137b7f..c0c4f4f 100644 --- a/test/unit/application/usecase/user/getUserByConfirmToken.test.js +++ b/test/unit/application/usecase/user/getUserByConfirmToken.test.js @@ -1,45 +1,49 @@ -const getUserByConfirmToken = require('../../../../../lib/application/use_cases/user/getUserByConfirmToken') +const getUserByConfirmToken = require("../../../../../lib/application/use_cases/user/getUserByConfirmToken"); const mockUser = { - id_utilisateur:1, - pseudo:'pseudo', - alias:'alias', - email:'testemail@gmail.com', - photo:'path/to/file', - photo_temporaire: 'path/to/file', - token:'token', - refresh_token:'refreshToken', - password:'password', - id_role:1, - ban_until:null, - confirmed:false, - confirm_token:'fezfezgezrhgez', - type:'user', -} + id_utilisateur: 1, + pseudo: "pseudo", + alias: "alias", + email: "testemail@gmail.com", + photo: "path/to/file", + photo_temporaire: "path/to/file", + token: "token", + refresh_token: "refreshToken", + password: "password", + id_role: 1, + ban_until: null, + confirmed: false, + confirm_token: "fezfezgezrhgez", + type: "user", +}; const expectedUser = { - id_utilisateur:1, - pseudo:'pseudo', - alias:'alias', - email:'testemail@gmail.com', - photo:'path/to/file', - photo_temporaire: 'path/to/file', - id_role:1, - ban_until:null, - type:'user', -} -const mockUserRepository = {} -describe('getUserByPseudo',()=>{ - afterEach(async () => { - jest.clearAllMocks(); + id_utilisateur: 1, + pseudo: "pseudo", + alias: "alias", + email: "testemail@gmail.com", + photo: `${process.env.API_URL}/path/to/file`, + photo_temporaire: `${process.env.API_URL}/path/to/file`, + id_role: 1, + ban_until: null, + type: "user", +}; +const mockUserRepository = {}; +describe("getUserByPseudo", () => { + afterEach(async () => { + jest.clearAllMocks(); + }); + it("should return Public User", async () => { + console.log("test"); + mockUserRepository.getByConfirmToken = jest.fn(() => mockUser); + const result = await getUserByConfirmToken("test", { + userRepository: mockUserRepository, }); - it('should return Public User',async ()=>{ - console.log("test") - mockUserRepository.getByConfirmToken = jest.fn(()=>mockUser) - const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) - expect(result).toEqual(expectedUser) - }) - it('should return null',async ()=>{ - mockUserRepository.getByConfirmToken = jest.fn(()=>null) - const result = await getUserByConfirmToken("test",{userRepository:mockUserRepository}) - expect(result).toBeNull() - }) -}) \ No newline at end of file + expect(result).toEqual(expectedUser); + }); + it("should return null", async () => { + mockUserRepository.getByConfirmToken = jest.fn(() => null); + const result = await getUserByConfirmToken("test", { + userRepository: mockUserRepository, + }); + expect(result).toBeNull(); + }); +}); diff --git a/test/unit/application/usecase/user/getUserByPseudo.test.js b/test/unit/application/usecase/user/getUserByPseudo.test.js index e77b1cf..fa1e35d 100644 --- a/test/unit/application/usecase/user/getUserByPseudo.test.js +++ b/test/unit/application/usecase/user/getUserByPseudo.test.js @@ -1,45 +1,49 @@ -const getUserBuPseudo = require('../../../../../lib/application/use_cases/user/getUserByPseudo') +require("dotenv").config(); +const getUserBuPseudo = require("../../../../../lib/application/use_cases/user/getUserByPseudo"); const mockUser = { - id_utilisateur:1, - pseudo:'pseudo', - alias:'alias', - email:'testemail@gmail.com', - photo:'path/to/file', - photo_temporaire: 'path/to/file', - token:'token', - refresh_token:'refreshToken', - password:'password', - id_role:1, - ban_until:null, - confirmed:false, - confirm_token:'fezfezgezrhgez', - type:'user', -} + id_utilisateur: 1, + pseudo: "pseudo", + alias: "alias", + email: "testemail@gmail.com", + photo: "path/to/file", + photo_temporaire: "path/to/file", + token: "token", + refresh_token: "refreshToken", + password: "password", + id_role: 1, + ban_until: null, + confirmed: false, + confirm_token: "fezfezgezrhgez", + type: "user", +}; const expectedUser = { - id_utilisateur:1, - pseudo:'pseudo', - alias:'alias', - email:'testemail@gmail.com', - photo:'path/to/file', - photo_temporaire: 'path/to/file', - id_role:1, - ban_until:null, - type:'user', -} -const mockUserRepository = {} -describe('getUserByPseudo',()=>{ - afterEach(async () => { - jest.clearAllMocks(); + id_utilisateur: 1, + pseudo: "pseudo", + alias: "alias", + email: "testemail@gmail.com", + photo: `${process.env.API_URL}/path/to/file`, + photo_temporaire: `${process.env.API_URL}/path/to/file`, + id_role: 1, + ban_until: null, + type: "user", +}; +const mockUserRepository = {}; +describe("getUserByPseudo", () => { + afterEach(async () => { + jest.clearAllMocks(); + }); + it("should return Public User", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(() => mockUser); + const result = await getUserBuPseudo("test", { + userRepository: mockUserRepository, }); - it('should return Public User',async ()=>{ - console.log("test") - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>mockUser) - const result = await getUserBuPseudo("test",{userRepository:mockUserRepository}) - expect(result).toEqual(expectedUser) - }) - it('should return null',async ()=>{ - mockUserRepository.getByEmailOrPseudo = jest.fn(()=>null) - const result = await getUserBuPseudo("test",{userRepository:mockUserRepository}) - expect(result).toBeNull() - }) -}) \ No newline at end of file + expect(result).toEqual(expectedUser); + }); + it("should return null", async () => { + mockUserRepository.getByEmailOrPseudo = jest.fn(() => null); + const result = await getUserBuPseudo("test", { + userRepository: mockUserRepository, + }); + expect(result).toBeNull(); + }); +}); diff --git a/test/unit/application/usecase/user/oeuvreFav.test.js b/test/unit/application/usecase/user/oeuvreFav.test.js index cb5b4bf..30afe5e 100644 --- a/test/unit/application/usecase/user/oeuvreFav.test.js +++ b/test/unit/application/usecase/user/oeuvreFav.test.js @@ -1,223 +1,239 @@ -const OeuvreFav = require("../../../../../lib/application/use_cases/user/oeuvreFav.js") -const throwStatusCode = require("../../../../../lib/application/use_cases/utils/throwStatusCode.js") -const catchError = require("../utils/catchError.js") -const { albumRawOneArtist } = require("../../../interfaces/serializers/fixtures/albumFixture.js") -const { rawTrackWithOneArtist } = require("../../../interfaces/serializers/fixtures/albumTrackFixture.js") +const OeuvreFav = require("../../../../../lib/application/use_cases/user/oeuvreFav.js"); +const throwStatusCode = require("../../../../../lib/application/use_cases/utils/throwStatusCode.js"); +const catchError = require("../utils/catchError.js"); +const { + albumRawOneArtist, +} = require("../../../interfaces/serializers/fixtures/albumFixture.js"); +const { + rawTrackWithOneArtist, +} = require("../../../interfaces/serializers/fixtures/albumTrackFixture.js"); describe("OeuvreFav Test", () => { - const idOeuvre = 'idOeuvre' - const userToken = 'token' - - const mockAccesTokenManager = {} - const mockSpotifyRepository = {} - const mockUserRepository = {} - const mockOeuvreFavRepository = {} - const serviceLocator = { - userRepository: mockUserRepository, - oeuvreFavRepository: mockOeuvreFavRepository, - accessTokenManager: mockAccesTokenManager, - spotifyRepository: mockSpotifyRepository, - } - - describe("invalid and valid cases", () => { - - it("should throw error bad auth token", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => null) - const error = await catchError(async () => { - await OeuvreFav(userToken, idOeuvre, serviceLocator) - }) - expect(error.code).toBe(401) - }) - - // aucune oeuvre de trouve - it("should throw incrorrect id oeuvre error", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - throwStatusCode("400", "invalid id"); - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - throwStatusCode("400", "invalid id"); - }) - const error = await catchError(async () => { - await OeuvreFav(userToken, idOeuvre, serviceLocator) - }) - expect(error.code).toBe(404) - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - }) - - // plus de 3 oeuvres favorites avec album - it("should throw maximal add", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return Promise.resolve(albumRawOneArtist); - }); - mockSpotifyRepository.getSpotifyTracks = jest.fn(); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) - - const error = await catchError(async () => { - await OeuvreFav(userToken, idOeuvre, serviceLocator) - }) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(error.code).toBe(403) - }) - - // plus de 3 oeuvres favorites avec track - it("should throw maximal add", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - throwStatusCode("400", "invalid id"); - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return Promise.resolve(rawTrackWithOneArtist); - }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => false) - - const error = await catchError(async () => { - await OeuvreFav(userToken, idOeuvre, serviceLocator) - }) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(error.code).toBe(403) - }) - - // ajout réussie avec album - it("should put an album", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return Promise.resolve(albumRawOneArtist); - }); - mockSpotifyRepository.getSpotifyTracks = jest.fn(); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - - const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled() - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) - expect(result).toEqual(true) - }) - - // ajout réussie avec track - it("should put an track", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - throwStatusCode("400", "invalid id"); - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return Promise.resolve(rawTrackWithOneArtist); - }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => false) - mockOeuvreFavRepository.ajoutPossible = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - - const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1) - expect(result).toEqual(true) - }) - - - - // supression réussite d'un album - it("should remove an album ", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - return Promise.resolve(albumRawOneArtist); - }); - mockSpotifyRepository.getSpotifyTracks = jest.fn(); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.ajoutPossible = jest.fn() - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() - - const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).not.toHaveBeenCalled(); - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); - expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) - expect(result).toEqual(false) - }) - - - // supression réussite d'une track - it("should remove a track ", async () => { - mockAccesTokenManager.decode = jest.fn((userToken) => 1) - mockUserRepository.getByUser = jest.fn((userToken) => { - return { - id_utilisateur: 1 - } - }) - mockSpotifyRepository.getSpotifyAlbums = jest.fn((idOeuvre) => { - throwStatusCode("400", "invalid id"); - }) - mockSpotifyRepository.getSpotifyTracks = jest.fn((idOeuvre) => { - return Promise.resolve(rawTrackWithOneArtist); - }); - mockOeuvreFavRepository.oeuvreFavExists = jest.fn((id_utilisateur, idOeuvre) => true) - mockOeuvreFavRepository.ajoutPossible = jest.fn() - mockOeuvreFavRepository.addOeuvrefav = jest.fn() - mockOeuvreFavRepository.deleteOeuvrefav = jest.fn() - - const result = await OeuvreFav(userToken, idOeuvre, serviceLocator) - - expect(mockSpotifyRepository.getSpotifyAlbums).toHaveBeenCalledTimes(1) - expect(mockSpotifyRepository.getSpotifyTracks).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.oeuvreFavExists).toHaveBeenCalledTimes(1) - expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled() - expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1) - expect(result).toEqual(false) - }) - - }) -}) + const idOeuvre = "idOeuvre"; + const userToken = "token"; + + const mockAccesTokenManager = {}; + const mockSpotifyRepository = {}; + const mockUserRepository = {}; + const mockOeuvreFavRepository = {}; + const serviceLocator = { + userRepository: mockUserRepository, + oeuvreFavRepository: mockOeuvreFavRepository, + accessTokenManager: mockAccesTokenManager, + spotifyRepository: mockSpotifyRepository, + }; + + describe("invalid and valid cases", () => { + it("should throw error bad auth token", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => null); + const error = await catchError(async () => { + await OeuvreFav(userToken, "track", idOeuvre, serviceLocator); + }); + expect(error.code).toBe(401); + }); + + // aucune oeuvre de trouve + it("should throw incrorrect id oeuvre error", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + throwStatusCode(404, "invalid id"); + }); + const error = await catchError(async () => { + await OeuvreFav(userToken, "track", idOeuvre, serviceLocator); + }); + expect(error.code).toBe(404); + + expect(mockSpotifyRepository.getOeuvre).toHaveBeenCalledTimes(1); + }); + + // plus de 3 oeuvres favorites avec album + it("should throw maximal add", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + + const error = await catchError(async () => { + await OeuvreFav(userToken, "track", idOeuvre, serviceLocator); + }); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(error.code).toBe(403); + }); + + // plus de 3 oeuvres favorites avec track + it("should throw maximal add", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + + const error = await catchError(async () => { + await OeuvreFav(userToken, "track", idOeuvre, serviceLocator); + }); + + expect(mockSpotifyRepository.getOeuvre).toHaveBeenCalledTimes(1); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(error.code).toBe(403); + }); + + // ajout réussie avec album + it("should put an album", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + + const result = await OeuvreFav( + userToken, + "track", + idOeuvre, + serviceLocator + ); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1); + expect(result).toEqual(true); + }); + + // ajout réussie avec track + it("should put an track", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => false + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + + const result = await OeuvreFav( + userToken, + "track", + idOeuvre, + serviceLocator + ); + + expect(mockSpotifyRepository.getOeuvre).toHaveBeenCalledTimes(1); + + expect(mockOeuvreFavRepository.ajoutPossible).toHaveBeenCalledTimes(1); + expect(mockOeuvreFavRepository.addOeuvrefav).toHaveBeenCalledTimes(1); + expect(result).toEqual(true); + }); + + // supression réussite d'un album + it("should remove an album ", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(albumRawOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn(); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn(); + + const result = await OeuvreFav( + userToken, + "track", + idOeuvre, + serviceLocator + ); + + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1); + expect(result).toEqual(false); + }); + + // supression réussite d'une track + it("should remove a track ", async () => { + mockAccesTokenManager.decode = jest.fn((userToken) => 1); + mockUserRepository.getByUser = jest.fn((userToken) => { + return { + id_utilisateur: 1, + }; + }); + + mockSpotifyRepository.getOeuvre = jest.fn((idOeuvre) => { + return Promise.resolve(rawTrackWithOneArtist); + }); + mockOeuvreFavRepository.oeuvreFavExists = jest.fn( + (id_utilisateur, idOeuvre) => true + ); + mockOeuvreFavRepository.ajoutPossible = jest.fn(); + mockOeuvreFavRepository.addOeuvrefav = jest.fn(); + mockOeuvreFavRepository.deleteOeuvrefav = jest.fn(); + + const result = await OeuvreFav( + userToken, + "track", + idOeuvre, + serviceLocator + ); + + expect(mockSpotifyRepository.getOeuvre).toHaveBeenCalledTimes(1); + + expect(mockOeuvreFavRepository.ajoutPossible).not.toHaveBeenCalled(); + expect(mockOeuvreFavRepository.deleteOeuvrefav).toHaveBeenCalledTimes(1); + expect(result).toEqual(false); + }); + }); +}); From 9c8555e8b5a5799297c8d687ba5d4a0be12a3dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Thu, 9 May 2024 21:05:39 +0200 Subject: [PATCH 64/67] fixed navigation --- lib/application/use_cases/review/likeReview.js | 2 +- lib/application/use_cases/user/getPage.js | 18 +++++------------- .../repositories/CommentRepository.js | 2 +- lib/interfaces/serializers/ReviewSerializer.js | 2 +- test/integration/artist.test.js | 2 +- test/integration/review.test.js | 2 +- .../usecase/artist/getArtist.test.js | 6 +++--- .../usecase/oeuvre/getOeuvre.test.js | 8 ++++---- .../usecase/review/getOeuvreReviews.test.js | 2 +- .../usecase/review/getReview.test.js | 2 +- .../usecase/review/getReviews.test.js | 4 ++-- .../usecase/review/getUserReviews.test.js | 2 +- .../usecase/review/likeReview.test.js | 4 ++-- 13 files changed, 24 insertions(+), 32 deletions(-) diff --git a/lib/application/use_cases/review/likeReview.js b/lib/application/use_cases/review/likeReview.js index e1c501e..5aed82e 100644 --- a/lib/application/use_cases/review/likeReview.js +++ b/lib/application/use_cases/review/likeReview.js @@ -2,7 +2,7 @@ const throwStatusCode = require("../utils/throwStatusCode"); module.exports = async (reviewId,userToken,{accessTokenManager,userRepository,reviewRepository}) =>{ const id_utilisateur = accessTokenManager.decode(userToken)?.value if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - if(! await reviewRepository.doesUserLike(id_utilisateur,reviewId)){ + if(! await reviewRepository.doesUserLikes(id_utilisateur,reviewId)){ await reviewRepository.likeReview(id_utilisateur,reviewId) return true } diff --git a/lib/application/use_cases/user/getPage.js b/lib/application/use_cases/user/getPage.js index 66f0487..285e790 100644 --- a/lib/application/use_cases/user/getPage.js +++ b/lib/application/use_cases/user/getPage.js @@ -65,21 +65,13 @@ module.exports = async ( const reviewsPromise = Promise.all( reviewsRaw.map(async (review) => { - const rawOeuvre = await spotifyRepository.getOeuvre( - review.id_oeuvre, - review.type - ); - if (rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); - const doesUserLike = await reviewRepository.doesUserLike( - id_utilisateur, - review.id_review - ); return reviewSerializer( review, - rawOeuvre, - review.utilisateur, - doesUserLike + id_utilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository ); }) ); diff --git a/lib/infrastructure/repositories/CommentRepository.js b/lib/infrastructure/repositories/CommentRepository.js index cd8c6f2..aab2798 100644 --- a/lib/infrastructure/repositories/CommentRepository.js +++ b/lib/infrastructure/repositories/CommentRepository.js @@ -192,7 +192,7 @@ module.exports = class extends CommentRepository { whereClause.id_utilisateur = id_utilisateur ? { [Op.in]: sequelize.literal( - `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id_utilisateur} and en_attente = false))` ), } : { diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js index 062f19c..8ed09d2 100644 --- a/lib/interfaces/serializers/ReviewSerializer.js +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -13,7 +13,7 @@ const serializeReview = async ( ) => { let doesUserLike = false; if (id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike( + doesUserLike = await reviewRepository.doesUserLikes( id_utilisateur, rawReview.id_review ); diff --git a/test/integration/artist.test.js b/test/integration/artist.test.js index 2e9286a..f022f71 100644 --- a/test/integration/artist.test.js +++ b/test/integration/artist.test.js @@ -69,7 +69,7 @@ describe('artist route', () => { mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const res1 = await server.inject({ diff --git a/test/integration/review.test.js b/test/integration/review.test.js index b2ec066..52232cc 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -138,7 +138,7 @@ describe('review route', () => { it("should return status code 200 with user login", async ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) const res1 = await server.inject({ method: 'GET', diff --git a/test/unit/application/usecase/artist/getArtist.test.js b/test/unit/application/usecase/artist/getArtist.test.js index b886d92..14a86aa 100644 --- a/test/unit/application/usecase/artist/getArtist.test.js +++ b/test/unit/application/usecase/artist/getArtist.test.js @@ -35,7 +35,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const result = await getArtist(1,'token', serviceLocator) @@ -80,7 +80,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ error: { @@ -106,7 +106,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ error: { diff --git a/test/unit/application/usecase/oeuvre/getOeuvre.test.js b/test/unit/application/usecase/oeuvre/getOeuvre.test.js index 8bc9531..a29be6d 100644 --- a/test/unit/application/usecase/oeuvre/getOeuvre.test.js +++ b/test/unit/application/usecase/oeuvre/getOeuvre.test.js @@ -49,7 +49,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getReviewCount = jest.fn((idOeuvre) => 2) mockReviewRepository.getOeuvreRating = jest.fn(() => 1) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(false) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(false) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) //mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) @@ -57,7 +57,7 @@ describe("getOeuvre Test", ()=>{ // mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) //mockReviewRepository.getReviewCount = jest.fn((id) => 2) //mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - // mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + // mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) // mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) // mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const result = await getOeuvre(1,'token', serviceLocator) @@ -102,7 +102,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ error: { @@ -128,7 +128,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ error: { diff --git a/test/unit/application/usecase/review/getOeuvreReviews.test.js b/test/unit/application/usecase/review/getOeuvreReviews.test.js index 65c1639..3346f7c 100644 --- a/test/unit/application/usecase/review/getOeuvreReviews.test.js +++ b/test/unit/application/usecase/review/getOeuvreReviews.test.js @@ -30,7 +30,7 @@ describe("getReviews Test", ()=>{ }) mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getOeuvreReviews(1,'token',1,10,true, serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/getReview.test.js b/test/unit/application/usecase/review/getReview.test.js index e7bbaee..54ef6f9 100644 --- a/test/unit/application/usecase/review/getReview.test.js +++ b/test/unit/application/usecase/review/getReview.test.js @@ -39,7 +39,7 @@ describe("getReview Test", ()=>{ mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const result = await getReview(1,'something',1,10,true, serviceLocator) expect(result).toEqual(expectedPrivate) }) diff --git a/test/unit/application/usecase/review/getReviews.test.js b/test/unit/application/usecase/review/getReviews.test.js index 39d0d1d..32c9cdb 100644 --- a/test/unit/application/usecase/review/getReviews.test.js +++ b/test/unit/application/usecase/review/getReviews.test.js @@ -19,7 +19,7 @@ describe("getReviews Test", ()=>{ mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,undefined, serviceLocator) expect(result).toEqual(expectedReviews) @@ -29,7 +29,7 @@ describe("getReviews Test", ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,'something', serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js index 74af95f..2bf786a 100644 --- a/test/unit/application/usecase/review/getUserReviews.test.js +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -46,7 +46,7 @@ describe("getReviews Test", ()=>{ mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getUserReviews(1,'token',1,10,true, serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/likeReview.test.js b/test/unit/application/usecase/review/likeReview.test.js index 8be531a..ce3f64e 100644 --- a/test/unit/application/usecase/review/likeReview.test.js +++ b/test/unit/application/usecase/review/likeReview.test.js @@ -19,7 +19,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLike = jest.fn((id) => true) + mockreviewRepository.doesUserLikes = jest.fn((id) => true) mockReviewRepository.unlikeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.unlikeReview).toHaveBeenCalledTimes(1) @@ -32,7 +32,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLike = jest.fn((id) => false) + mockreviewRepository.doesUserLikes = jest.fn((id) => false) mockReviewRepository.likeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.likeReview).toHaveBeenCalledTimes(1) From fc31c6f17ffd0e7cd1e8088236b025d822de1f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Fri, 10 May 2024 22:47:15 +0200 Subject: [PATCH 65/67] 2 requests friends page (#58) --- .../use_cases/friend/getListFriendsRequest.js | 13 ++++++++++--- lib/domain/model/ListsFriendsRequestsPage.js | 6 ++++++ .../repositories/FriendRepository.js | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 lib/domain/model/ListsFriendsRequestsPage.js diff --git a/lib/application/use_cases/friend/getListFriendsRequest.js b/lib/application/use_cases/friend/getListFriendsRequest.js index b1ad38c..9f6cd16 100644 --- a/lib/application/use_cases/friend/getListFriendsRequest.js +++ b/lib/application/use_cases/friend/getListFriendsRequest.js @@ -1,6 +1,9 @@ 'use strict'; const throwStatusCode = require("../utils/throwStatusCode") +const UserPublic = require("../../../domain/model/UserPublic.js"); +const ListsFriendsRequestsPage = require("../../../domain/model/ListsFriendsRequestsPage.js"); + module.exports = async (token, {accessTokenManager, userRepository, friendRepository}) => { const id = accessTokenManager.decode(token)?.value @@ -9,7 +12,11 @@ module.exports = async (token, {accessTokenManager, userRepository, friendReposi throwStatusCode(400,"Votre token d'authentification n'est pas le bon"); } - const requests = await friendRepository.getRequestFriendsById(id) - - return requests + const usersRequestsReceived = await friendRepository.getRequestFriendsById(id) + const usersRequestsSend = await friendRepository.getSendRequestFriendsById(id) + + const usersPrivateRequestsReceived = await Promise.all(usersRequestsReceived.map(async (userPublic) => new UserPublic(userPublic))); + const usersPrivateRequestsSend = await Promise.all(usersRequestsSend.map(async (userPublic) => new UserPublic(userPublic))); + + return new ListsFriendsRequestsPage(usersPrivateRequestsReceived, usersPrivateRequestsSend) } \ No newline at end of file diff --git a/lib/domain/model/ListsFriendsRequestsPage.js b/lib/domain/model/ListsFriendsRequestsPage.js new file mode 100644 index 0000000..6b718b4 --- /dev/null +++ b/lib/domain/model/ListsFriendsRequestsPage.js @@ -0,0 +1,6 @@ +module.exports = class { + constructor(usersPrivateRequestsReceived,usersPrivateRequestsSend){ + this.requestsReceived = usersPrivateRequestsReceived + this.requestsSend = usersPrivateRequestsSend + } +} \ No newline at end of file diff --git a/lib/infrastructure/repositories/FriendRepository.js b/lib/infrastructure/repositories/FriendRepository.js index 7a68beb..05fc0bf 100644 --- a/lib/infrastructure/repositories/FriendRepository.js +++ b/lib/infrastructure/repositories/FriendRepository.js @@ -71,6 +71,23 @@ module.exports = class extends FriendRepositoryAbstract { amiIdUtilisateur: id, }, }); + const friends = await this.UserModel.findAll({ + where: { + id_utilisateur: { + [Op.in]: idFriends.map((f) => f.getDataValue("id_utilisateur")), + }, + }, + }); + return friends ? friends.map((f) => new User(f)) : null; + } + + async getSendRequestFriendsById(id) { + const idFriends = await this.model.findAll({ + where: { + en_attente: true, + id_utilisateur: id, + }, + }); const friends = await this.UserModel.findAll({ where: { id_utilisateur: { From ea3c70af8df7c704205910f2b3c464a901a68bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a?= <75787565+Hiyorie@users.noreply.github.com> Date: Fri, 10 May 2024 22:47:32 +0200 Subject: [PATCH 66/67] Feature/followers artist (#59) * fix getComment * non prise en compte des commentaires deleted * get artist's followers without tests * FIX get artist's followers * resolution de conflits * fix getComment * Fix du repos comment + ajoute du doesFollow des artistes d'un oeuvre --------- Co-authored-by: yousrah24 --- lib/application/use_cases/oeuvre/getOeuvre.js | 8 +++++++- lib/infrastructure/repositories/CommentRepository.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/application/use_cases/oeuvre/getOeuvre.js b/lib/application/use_cases/oeuvre/getOeuvre.js index 2725d97..2a1c07d 100644 --- a/lib/application/use_cases/oeuvre/getOeuvre.js +++ b/lib/application/use_cases/oeuvre/getOeuvre.js @@ -16,6 +16,7 @@ module.exports = async ( likeOeuvreRepository, oeuvreFavRepository, friendRepository, + followRepository } ) => { const idUtilisateur = accessTokenManager.decode(userToken)?.value; @@ -39,7 +40,12 @@ module.exports = async ( const artists = await Promise.all( artistIds.map(async (id) => { - return await fetchArtist(id, { spotifyRepository }); + const artist = await fetchArtist(id, { spotifyRepository }); + artist.doesUserFollow = await followRepository.doesFollows( + idUtilisateur, + id + ); + return artist; }) ); diff --git a/lib/infrastructure/repositories/CommentRepository.js b/lib/infrastructure/repositories/CommentRepository.js index cd8c6f2..aab2798 100644 --- a/lib/infrastructure/repositories/CommentRepository.js +++ b/lib/infrastructure/repositories/CommentRepository.js @@ -192,7 +192,7 @@ module.exports = class extends CommentRepository { whereClause.id_utilisateur = id_utilisateur ? { [Op.in]: sequelize.literal( - `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id} and en_attente = false))` + `(SELECT id_utilisateur FROM utilisateur WHERE is_private = false OR id_utilisateur=${id_utilisateur} OR id_utilisateur IN (SELECT id_utilisateur FROM amis WHERE amiIdUtilisateur = ${id_utilisateur} and en_attente = false) OR id_utilisateur IN (SELECT amiIdUtilisateur FROM amis WHERE id_utilisateur = ${id_utilisateur} and en_attente = false))` ), } : { From 72a55bae92ae54d07f4e9a661c5bca393dd8ab23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Chanon?= <95433353+MaelChanon@users.noreply.github.com> Date: Thu, 9 May 2024 21:05:39 +0200 Subject: [PATCH 67/67] fixed navigation --- lib/application/use_cases/review/likeReview.js | 2 +- lib/application/use_cases/user/getPage.js | 18 +++++------------- lib/interfaces/serializers/ReviewSerializer.js | 2 +- test/integration/artist.test.js | 2 +- test/integration/review.test.js | 2 +- .../usecase/artist/getArtist.test.js | 6 +++--- .../usecase/oeuvre/getOeuvre.test.js | 8 ++++---- .../usecase/review/getOeuvreReviews.test.js | 2 +- .../usecase/review/getReview.test.js | 2 +- .../usecase/review/getReviews.test.js | 4 ++-- .../usecase/review/getUserReviews.test.js | 2 +- .../usecase/review/likeReview.test.js | 4 ++-- 12 files changed, 23 insertions(+), 31 deletions(-) diff --git a/lib/application/use_cases/review/likeReview.js b/lib/application/use_cases/review/likeReview.js index e1c501e..5aed82e 100644 --- a/lib/application/use_cases/review/likeReview.js +++ b/lib/application/use_cases/review/likeReview.js @@ -2,7 +2,7 @@ const throwStatusCode = require("../utils/throwStatusCode"); module.exports = async (reviewId,userToken,{accessTokenManager,userRepository,reviewRepository}) =>{ const id_utilisateur = accessTokenManager.decode(userToken)?.value if(! await userRepository.getByUser(id_utilisateur)) throwStatusCode(401,"votre token d'authentification n'est pas le bon") - if(! await reviewRepository.doesUserLike(id_utilisateur,reviewId)){ + if(! await reviewRepository.doesUserLikes(id_utilisateur,reviewId)){ await reviewRepository.likeReview(id_utilisateur,reviewId) return true } diff --git a/lib/application/use_cases/user/getPage.js b/lib/application/use_cases/user/getPage.js index 66f0487..285e790 100644 --- a/lib/application/use_cases/user/getPage.js +++ b/lib/application/use_cases/user/getPage.js @@ -65,21 +65,13 @@ module.exports = async ( const reviewsPromise = Promise.all( reviewsRaw.map(async (review) => { - const rawOeuvre = await spotifyRepository.getOeuvre( - review.id_oeuvre, - review.type - ); - if (rawOeuvre.error) - throwStatusCode(rawOeuvre.error.status, rawOeuvre.error.message); - const doesUserLike = await reviewRepository.doesUserLike( - id_utilisateur, - review.id_review - ); return reviewSerializer( review, - rawOeuvre, - review.utilisateur, - doesUserLike + id_utilisateur, + undefined, + spotifyRepository, + reviewRepository, + friendRepository ); }) ); diff --git a/lib/interfaces/serializers/ReviewSerializer.js b/lib/interfaces/serializers/ReviewSerializer.js index 062f19c..8ed09d2 100644 --- a/lib/interfaces/serializers/ReviewSerializer.js +++ b/lib/interfaces/serializers/ReviewSerializer.js @@ -13,7 +13,7 @@ const serializeReview = async ( ) => { let doesUserLike = false; if (id_utilisateur) { - doesUserLike = await reviewRepository.doesUserLike( + doesUserLike = await reviewRepository.doesUserLikes( id_utilisateur, rawReview.id_review ); diff --git a/test/integration/artist.test.js b/test/integration/artist.test.js index 2e9286a..f022f71 100644 --- a/test/integration/artist.test.js +++ b/test/integration/artist.test.js @@ -69,7 +69,7 @@ describe('artist route', () => { mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const res1 = await server.inject({ diff --git a/test/integration/review.test.js b/test/integration/review.test.js index b2ec066..52232cc 100644 --- a/test/integration/review.test.js +++ b/test/integration/review.test.js @@ -138,7 +138,7 @@ describe('review route', () => { it("should return status code 200 with user login", async ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) const res1 = await server.inject({ method: 'GET', diff --git a/test/unit/application/usecase/artist/getArtist.test.js b/test/unit/application/usecase/artist/getArtist.test.js index b886d92..14a86aa 100644 --- a/test/unit/application/usecase/artist/getArtist.test.js +++ b/test/unit/application/usecase/artist/getArtist.test.js @@ -35,7 +35,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const result = await getArtist(1,'token', serviceLocator) @@ -80,7 +80,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ error: { @@ -106,7 +106,7 @@ describe("getArtist Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ error: { diff --git a/test/unit/application/usecase/oeuvre/getOeuvre.test.js b/test/unit/application/usecase/oeuvre/getOeuvre.test.js index 8bc9531..a29be6d 100644 --- a/test/unit/application/usecase/oeuvre/getOeuvre.test.js +++ b/test/unit/application/usecase/oeuvre/getOeuvre.test.js @@ -49,7 +49,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getReviewCount = jest.fn((idOeuvre) => 2) mockReviewRepository.getOeuvreRating = jest.fn(() => 1) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(false) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(false) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) //mockSpotifyRepository.getSpotifyArtist = jest.fn((artistId) => mockArtist) @@ -57,7 +57,7 @@ describe("getOeuvre Test", ()=>{ // mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) //mockReviewRepository.getReviewCount = jest.fn((id) => 2) //mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - // mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + // mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) // mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) // mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue(mockOeuvreReviewSpotify) const result = await getOeuvre(1,'token', serviceLocator) @@ -102,7 +102,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValue({ error: { @@ -128,7 +128,7 @@ describe("getOeuvre Test", ()=>{ mockReviewRepository.getOeuvreRating = jest.fn((id) => 1) mockReviewRepository.getReviewCount = jest.fn((id) => 2) mockReviewRepository.getOeuvreReviews = jest.fn().mockReturnValueOnce([mockLikedReview]).mockReturnValueOnce([mockCommentedReview]) - mockReviewRepository.doesUserLike = jest.fn().mockReturnValue(true) + mockreviewRepository.doesUserLikes = jest.fn().mockReturnValue(true) mockFollowRepository.doesFollows = jest.fn().mockReturnValue(true) mockSpotifyRepository.getOeuvre = jest.fn().mockReturnValueOnce(mockOeuvreReviewSpotify).mockReturnValue({ error: { diff --git a/test/unit/application/usecase/review/getOeuvreReviews.test.js b/test/unit/application/usecase/review/getOeuvreReviews.test.js index 65c1639..3346f7c 100644 --- a/test/unit/application/usecase/review/getOeuvreReviews.test.js +++ b/test/unit/application/usecase/review/getOeuvreReviews.test.js @@ -30,7 +30,7 @@ describe("getReviews Test", ()=>{ }) mockReviewRepository.getOeuvreReviews = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getOeuvreReviews(1,'token',1,10,true, serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/getReview.test.js b/test/unit/application/usecase/review/getReview.test.js index e7bbaee..54ef6f9 100644 --- a/test/unit/application/usecase/review/getReview.test.js +++ b/test/unit/application/usecase/review/getReview.test.js @@ -39,7 +39,7 @@ describe("getReview Test", ()=>{ mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const result = await getReview(1,'something',1,10,true, serviceLocator) expect(result).toEqual(expectedPrivate) }) diff --git a/test/unit/application/usecase/review/getReviews.test.js b/test/unit/application/usecase/review/getReviews.test.js index 39d0d1d..32c9cdb 100644 --- a/test/unit/application/usecase/review/getReviews.test.js +++ b/test/unit/application/usecase/review/getReviews.test.js @@ -19,7 +19,7 @@ describe("getReviews Test", ()=>{ mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,undefined, serviceLocator) expect(result).toEqual(expectedReviews) @@ -29,7 +29,7 @@ describe("getReviews Test", ()=>{ mockAccesTokenManager.decode = jest.fn((token) => {return {value: 1}}) mockReviewRepository.getReviews = jest.fn((page,pageSize,orderByLike, isPrivate,userToken) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getReviews(1,10,true,'something', serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/getUserReviews.test.js b/test/unit/application/usecase/review/getUserReviews.test.js index 74af95f..2bf786a 100644 --- a/test/unit/application/usecase/review/getUserReviews.test.js +++ b/test/unit/application/usecase/review/getUserReviews.test.js @@ -46,7 +46,7 @@ describe("getReviews Test", ()=>{ mockFriendRepository.areFriends = jest.fn((id, id_ami) => true) mockReviewRepository.getReviewByUserId = jest.fn((id_utilisateur,page,pageSize,orderByLike) => [rawReview]) mockSpotifyRepository.getOeuvre = jest.fn((id,type) => mockArtist) - mockReviewRepository.doesUserLike = jest.fn((id_utilisateur,reviewId) => false) + mockreviewRepository.doesUserLikes = jest.fn((id_utilisateur,reviewId) => false) const expectedReviews = [expectedReview] const result = await getUserReviews(1,'token',1,10,true, serviceLocator) expect(result).toEqual(expectedReviews) diff --git a/test/unit/application/usecase/review/likeReview.test.js b/test/unit/application/usecase/review/likeReview.test.js index 8be531a..ce3f64e 100644 --- a/test/unit/application/usecase/review/likeReview.test.js +++ b/test/unit/application/usecase/review/likeReview.test.js @@ -19,7 +19,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLike = jest.fn((id) => true) + mockreviewRepository.doesUserLikes = jest.fn((id) => true) mockReviewRepository.unlikeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.unlikeReview).toHaveBeenCalledTimes(1) @@ -32,7 +32,7 @@ describe("likeReview Test", ()=>{ id_utilisateur: 1 } }) - mockReviewRepository.doesUserLike = jest.fn((id) => false) + mockreviewRepository.doesUserLikes = jest.fn((id) => false) mockReviewRepository.likeReview = jest.fn((id) => true) await likeReview(1,'token', serviceLocator) expect(mockReviewRepository.likeReview).toHaveBeenCalledTimes(1)