diff --git a/.env-example b/.env-example index 110a656..780c8ad 100644 --- a/.env-example +++ b/.env-example @@ -20,6 +20,10 @@ TIMEZONE=America/Recife JWT_SECRET= JWT_EXPIRES_IN=9999999999999s +### EMAIL ################################################# +SENDGRID_API_KEY= +EMAIL_FROM= + ### GRAPHQL ################################################# GRAPHQL_DEBUG=true diff --git a/README.md b/README.md index 1427198..536b022 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # MyApi +### Includes + +SendGrid library to send emails + +> Add your sendgrid key on .env and choose the sendgrip controller + # Testing ## OpenApi diff --git a/config/config.js b/config/config.js index a47c5c3..f4d004f 100644 --- a/config/config.js +++ b/config/config.js @@ -8,6 +8,7 @@ module.exports = { database: process.env.DB_NAME, host: process.env.DB_HOST, dialect: 'mysql', + migrationStorageTableName: 'sequelize_meta', }, test: { username: process.env.DB_USER, @@ -15,6 +16,7 @@ module.exports = { database: process.env.DB_NAME, host: process.env.DB_HOST, dialect: 'mysql', + migrationStorageTableName: 'sequelize_meta', }, production: { username: process.env.DB_USER, @@ -22,5 +24,6 @@ module.exports = { database: process.env.DB_NAME, host: process.env.DB_HOST, dialect: 'mysql', + migrationStorageTableName: 'sequelize_meta', }, }; diff --git a/database/migrations/20210422223820-create-schema-people.js b/database/migrations/20210422223819-create-schema-users.js similarity index 73% rename from database/migrations/20210422223820-create-schema-people.js rename to database/migrations/20210422223819-create-schema-users.js index 19f75cc..9d04994 100644 --- a/database/migrations/20210422223820-create-schema-people.js +++ b/database/migrations/20210422223819-create-schema-users.js @@ -2,7 +2,7 @@ module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('people', { + await queryInterface.createTable('users', { id: { type: Sequelize.INTEGER, allowNull: false, @@ -36,6 +36,19 @@ module.exports = { type: Sequelize.STRING, allowNull: false, }, + login: { + type: Sequelize.STRING, + unique: true, + allowNull: false, + }, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + remember_token: { + type: Sequelize.STRING, + allowNull: true, + }, created_at: { type: Sequelize.DATE, allowNull: false, @@ -48,6 +61,6 @@ module.exports = { }, down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('people'); + await queryInterface.dropTable('users'); }, }; diff --git a/database/migrations/20210422223838-create-schema-addresses.js b/database/migrations/20210422223838-create-schema-addresses.js index c9652e8..a61d6b4 100644 --- a/database/migrations/20210422223838-create-schema-addresses.js +++ b/database/migrations/20210422223838-create-schema-addresses.js @@ -10,11 +10,11 @@ module.exports = { unique: true, primaryKey: true, }, - person_id: { + user_id: { type: Sequelize.INTEGER, allowNull: false, references: { - model: 'people', + model: 'users', key: 'id', }, onDelete: 'cascade', diff --git a/database/migrations/20210422223847-create-schema-contacts.js b/database/migrations/20210422223847-create-schema-contacts.js index 2c42549..3425176 100644 --- a/database/migrations/20210422223847-create-schema-contacts.js +++ b/database/migrations/20210422223847-create-schema-contacts.js @@ -10,11 +10,11 @@ module.exports = { unique: true, primaryKey: true, }, - person_id: { + user_id: { type: Sequelize.INTEGER, allowNull: false, references: { - model: 'people', + model: 'users', key: 'id', }, onDelete: 'cascade', diff --git a/database/migrations/20210423124742-create-schema-users.js b/database/migrations/20210423124742-create-schema-users.js deleted file mode 100644 index 95351e8..0000000 --- a/database/migrations/20210423124742-create-schema-users.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('users', { - id: { - type: Sequelize.INTEGER, - allowNull: false, - autoIncrement: true, - unique: true, - primaryKey: true, - }, - person_id: { - type: Sequelize.INTEGER, - allowNull: false, - references: { - model: 'people', - key: 'id', - }, - onDelete: 'cascade', - onUpdate: 'cascade', - }, - login: { - type: Sequelize.STRING, - unique: true, - allowNull: false, - }, - password: { - type: Sequelize.STRING, - allowNull: false, - }, - remember_token: { - type: Sequelize.STRING, - allowNull: true, - }, - created_at: { - type: Sequelize.DATE, - allowNull: false, - }, - updated_at: { - type: Sequelize.DATE, - allowNull: false, - }, - }); - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('users'); - }, -}; diff --git a/database/migrations/20210423130156-create-schema-modules.js b/database/migrations/20210423130156-create-schema-modules.js index 214e272..dcc8bab 100644 --- a/database/migrations/20210423130156-create-schema-modules.js +++ b/database/migrations/20210423130156-create-schema-modules.js @@ -22,6 +22,10 @@ module.exports = { type: Sequelize.STRING, allowNull: false, }, + submenus: { + type: Sequelize.JSON, + allowNull: true, + }, created_at: { type: Sequelize.DATE, allowNull: false, diff --git a/database/seeders/20210423133542-feed-people.js b/database/seeders/20210423133541-feed-users.js similarity index 72% rename from database/seeders/20210423133542-feed-people.js rename to database/seeders/20210423133541-feed-users.js index ca5809e..9719922 100644 --- a/database/seeders/20210423133542-feed-people.js +++ b/database/seeders/20210423133541-feed-users.js @@ -1,11 +1,11 @@ 'use strict'; module.exports = { - up: async (queryInterface) => { + up: async (queryInterface, Sequelize) => { const transaction = await queryInterface.sequelize.transaction(); try { await queryInterface.bulkInsert( - 'people', + 'users', [ { name: 'Admin 1', @@ -15,10 +15,14 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'admin@admin.com', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, { + id: 2, name: 'Manager', cpf: '817.535.687-14', email: 'manager@manager.com', @@ -26,10 +30,14 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'manager@manager.com', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, { + id: 3, name: 'Manager 2', cpf: '642.045.143-40', email: 'manager1@manager.com', @@ -37,6 +45,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'manager1@manager.com', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -48,6 +59,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'betinacamiladacruz_@utbr.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -59,6 +73,9 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'fernandopietronelsonassis-91@atiara.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -70,6 +87,9 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'levilorenzoramos-81@gruposandino.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -81,6 +101,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'hadass.hadassarebecaalicecortereal@leoshehtman.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -92,6 +115,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'isabelaadrianacarvalho_@elimco.com', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -103,6 +129,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'jjessicaluziateixeira@reconciliare.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -114,6 +143,9 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'marcosviniciuskaiquegaelfernandes@callan.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -125,6 +157,9 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'jorgemarcosmartins__jorgemarcosmartins@etec.sp.gov.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -136,6 +171,9 @@ module.exports = { gender: 'FEMALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'vitoriabeatrizdanielapires_@lnaa.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -147,6 +185,9 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'eedsonmarcoskaiquelopes@acesse.net', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -159,6 +200,10 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: + 'enzojoaquimerickdrumond__enzojoaquimerickdrumond@zipmail.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, @@ -170,11 +215,13 @@ module.exports = { gender: 'MALE', avatar: 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + login: 'alexandreosvaldomanuelcarvalho_@tursi.com.br', + password: + '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', created_at: new Date(), updated_at: new Date(), }, ], - { transaction, }, @@ -187,7 +234,7 @@ module.exports = { } }, - down: async (queryInterface) => { - await queryInterface.bulkDelete('people', null, {}); + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('users', null, {}); }, }; diff --git a/database/seeders/20210423135356-feed-addresses.js b/database/seeders/20210423135356-feed-addresses.js index 1aded82..252819b 100644 --- a/database/seeders/20210423135356-feed-addresses.js +++ b/database/seeders/20210423135356-feed-addresses.js @@ -14,7 +14,7 @@ module.exports = { neighborhood: 'São Gonçalo', city: 'Salvador', state: 'BA', - person_id: 1, + user_id: 1, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -27,7 +27,7 @@ module.exports = { neighborhood: 'Jaqueira', city: 'Tucuruí', state: 'PA', - person_id: 2, + user_id: 2, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -40,7 +40,7 @@ module.exports = { neighborhood: 'Mauazinho', city: 'Manaus', state: 'AM', - person_id: 3, + user_id: 3, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -53,7 +53,7 @@ module.exports = { neighborhood: 'Zélia Barbosa Rocha', city: 'Arapiraca', state: 'AL', - person_id: 4, + user_id: 4, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -66,7 +66,7 @@ module.exports = { neighborhood: 'Boqueirão', city: 'Guarapuava', state: 'PR', - person_id: 5, + user_id: 5, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -79,7 +79,7 @@ module.exports = { neighborhood: 'Parque Piauí', city: 'Timon', state: 'MA', - person_id: 6, + user_id: 6, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -92,7 +92,7 @@ module.exports = { neighborhood: 'Loteamento Campo Bello', city: 'Gurupi', state: 'TO', - person_id: 7, + user_id: 7, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -105,7 +105,7 @@ module.exports = { neighborhood: 'Coophatrabalho', city: 'Campo Grande', state: 'MS', - person_id: 8, + user_id: 8, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -118,7 +118,7 @@ module.exports = { neighborhood: 'Centro', city: 'Paulista', state: 'PE', - person_id: 9, + user_id: 9, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -131,7 +131,7 @@ module.exports = { neighborhood: 'Núcleo Residencial Porto Seguro', city: 'Itatiba', state: 'SP', - person_id: 10, + user_id: 10, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -144,7 +144,7 @@ module.exports = { neighborhood: 'Itaquera', city: 'São Paulo', state: 'SP', - person_id: 11, + user_id: 11, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -157,7 +157,7 @@ module.exports = { neighborhood: 'Boa União', city: 'Rio Branco', state: 'AC', - person_id: 12, + user_id: 12, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -170,7 +170,7 @@ module.exports = { neighborhood: 'Manoel Müller', city: 'Rolândia', state: 'PR', - person_id: 13, + user_id: 13, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -183,7 +183,7 @@ module.exports = { neighborhood: 'Jardim Copacabana', city: 'Aparecida de Goiânia', state: 'GO', - person_id: 14, + user_id: 14, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), @@ -196,7 +196,7 @@ module.exports = { neighborhood: 'Morada Nova', city: 'Uberlândia', state: 'MG', - person_id: 15, + user_id: 15, country: 'BRAZIL', created_at: new Date(), updated_at: new Date(), diff --git a/database/seeders/20210423140312-feed-contacts.js b/database/seeders/20210423140312-feed-contacts.js index 807bbdc..4b6e8c2 100644 --- a/database/seeders/20210423140312-feed-contacts.js +++ b/database/seeders/20210423140312-feed-contacts.js @@ -8,168 +8,168 @@ module.exports = { 'contacts', [ { - person_id: 1, + user_id: 1, contact_type: 'CELULAR', value: '(71) 998-577-479', created_at: new Date(), updated_at: new Date(), }, { - person_id: 1, + user_id: 1, contact_type: 'TWITTER', value: '@twitter-admin', created_at: new Date(), updated_at: new Date(), }, { - person_id: 2, + user_id: 2, contact_type: 'CELULAR', value: '(94) 925-073-336', created_at: new Date(), updated_at: new Date(), }, { - person_id: 2, + user_id: 2, contact_type: 'CELULAR', value: '(92) 937-696-796', created_at: new Date(), updated_at: new Date(), }, { - person_id: 3, + user_id: 3, contact_type: 'CELULAR', value: '(92) 987-566-796', created_at: new Date(), updated_at: new Date(), }, { - person_id: 3, + user_id: 3, contact_type: 'CELULAR', value: '(82) 999-685-606', created_at: new Date(), updated_at: new Date(), }, { - person_id: 4, + user_id: 4, contact_type: 'CELULAR', value: '(82) 931-151-606', created_at: new Date(), updated_at: new Date(), }, { - person_id: 5, + user_id: 5, contact_type: 'CELULAR', value: '(42) 936-669-368', created_at: new Date(), updated_at: new Date(), }, { - person_id: 6, + user_id: 6, contact_type: 'CELULAR', value: '(86) 988-133-382', created_at: new Date(), updated_at: new Date(), }, { - person_id: 7, + user_id: 7, contact_type: 'CELULAR', value: '(63) 965-486-006', created_at: new Date(), updated_at: new Date(), }, { - person_id: 7, + user_id: 7, contact_type: 'CELULAR', value: '(63) 999-554-009', created_at: new Date(), updated_at: new Date(), }, { - person_id: 7, + user_id: 7, contact_type: 'TWITTER', value: '@twitter', created_at: new Date(), updated_at: new Date(), }, { - person_id: 7, + user_id: 7, contact_type: 'FACEBOOK', value: '@facebook', created_at: new Date(), updated_at: new Date(), }, { - person_id: 8, + user_id: 8, contact_type: 'CELULAR', value: '(67) 992-533-292', created_at: new Date(), updated_at: new Date(), }, { - person_id: 9, + user_id: 9, contact_type: 'CELULAR', value: '(81) 985-727-845', created_at: new Date(), updated_at: new Date(), }, { - person_id: 10, + user_id: 10, contact_type: 'CELULAR', value: '(11) 943-773-279', created_at: new Date(), updated_at: new Date(), }, { - person_id: 11, + user_id: 11, contact_type: 'CELULAR', value: '(11) 975-800-032', created_at: new Date(), updated_at: new Date(), }, { - person_id: 12, + user_id: 12, contact_type: 'CELULAR', value: '(68) 930-911-911', created_at: new Date(), updated_at: new Date(), }, { - person_id: 13, + user_id: 13, contact_type: 'CELULAR', value: '(43) 988-777-000', created_at: new Date(), updated_at: new Date(), }, { - person_id: 13, + user_id: 13, contact_type: 'FIXO', value: '(62) 3398-4736', created_at: new Date(), updated_at: new Date(), }, { - person_id: 13, + user_id: 13, contact_type: 'FACEBOOK', value: '@facebook-2', created_at: new Date(), updated_at: new Date(), }, { - person_id: 13, + user_id: 13, contact_type: 'INSTAGRAM', value: '@instagram', created_at: new Date(), updated_at: new Date(), }, { - person_id: 14, + user_id: 14, contact_type: 'CELULAR', value: '(62) 978-249-436', created_at: new Date(), updated_at: new Date(), }, { - person_id: 15, + user_id: 15, contact_type: 'CELULAR', value: '(34) 992-150-179', created_at: new Date(), diff --git a/database/seeders/20210423142053-feed-users.js b/database/seeders/20210423142053-feed-users.js deleted file mode 100644 index 2e2558d..0000000 --- a/database/seeders/20210423142053-feed-users.js +++ /dev/null @@ -1,162 +0,0 @@ -'use strict'; - -module.exports = { - up: async (queryInterface, Sequelize) => { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.bulkInsert( - 'users', - [ - { - id: 1, - person_id: 1, - login: 'admin@admin.com', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 2, - person_id: 2, - login: 'manager@manager.com', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 3, - person_id: 3, - login: 'manager1@manager.com', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 4, - person_id: 4, - login: 'betinacamiladacruz_@utbr.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 5, - person_id: 5, - login: 'fernandopietronelsonassis-91@atiara.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 6, - person_id: 6, - login: 'levilorenzoramos-81@gruposandino.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 7, - person_id: 7, - login: 'hadass.hadassarebecaalicecortereal@leoshehtman.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 8, - person_id: 8, - login: 'isabelaadrianacarvalho_@elimco.com', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 9, - person_id: 9, - login: 'jjessicaluziateixeira@reconciliare.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 10, - person_id: 10, - login: 'marcosviniciuskaiquegaelfernandes@callan.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 11, - person_id: 11, - login: 'jorgemarcosmartins__jorgemarcosmartins@etec.sp.gov.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 12, - person_id: 12, - login: 'vitoriabeatrizdanielapires_@lnaa.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 13, - person_id: 13, - login: 'eedsonmarcoskaiquelopes@acesse.net', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 14, - person_id: 14, - login: - 'enzojoaquimerickdrumond__enzojoaquimerickdrumond@zipmail.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - { - id: 15, - person_id: 15, - login: 'alexandreosvaldomanuelcarvalho_@tursi.com.br', - password: - '$2b$10$z5V38BrCOClpwf.x7jm2uezhWq3icCS8lR6sKgebDSaEA5.KY6YEq', - created_at: new Date(), - updated_at: new Date(), - }, - ], - { - transaction, - }, - ); - - await transaction.commit(); - } catch (e) { - console.log(e); - await transaction.rollback(); - } - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('users', null, {}); - }, -}; diff --git a/database/seeders/20210423143352-feed-modules.js b/database/seeders/20210423143352-feed-modules.js index 70a686b..512dc1a 100644 --- a/database/seeders/20210423143352-feed-modules.js +++ b/database/seeders/20210423143352-feed-modules.js @@ -12,6 +12,8 @@ module.exports = { title: 'ACL', router: '/acl', description: 'ACL', + submenus: + '{"menus":[{"title":"Criar","router":"register"},{"title":"Listar","router":"list"}]}', created_at: new Date(), updated_at: new Date(), }, @@ -20,6 +22,8 @@ module.exports = { title: 'Usuários', router: '/users', description: 'users', + submenus: + '{"menus":[{"title":"Criar","router":"register"},{"title":"Listar","router":"list"}]}', created_at: new Date(), updated_at: new Date(), }, diff --git a/package-lock.json b/package-lock.json index d9a9e7d..38c44ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/platform-express": "^7.6.15", "@nestjs/sequelize": "^0.2.0", "@nestjs/swagger": "^4.8.0", + "@sendgrid/mail": "^7.4.4", "apollo-server-express": "^2.23.0", "bcrypt": "^5.0.1", "class-transformer": "0.4.0", @@ -38,7 +39,7 @@ "devDependencies": { "@nestjs/cli": "^7.6.0", "@nestjs/schematics": "^7.3.0", - "@nestjs/testing": "^7.6.15", + "@nestjs/testing": "^7.6.17", "@types/bcrypt": "^3.0.1", "@types/express": "^4.17.11", "@types/jest": "^26.0.23", @@ -2797,13 +2798,13 @@ } }, "node_modules/@nestjs/testing": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.15.tgz", - "integrity": "sha512-AWr8stnRoS0yCU/EotAJKveYXm1XSB4ZT+ReqFDXhPyqC1ppOfU+zZuCjQlQHorMzidsVHsAstK5/rUsXnrZUQ==", + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.17.tgz", + "integrity": "sha512-wWImNvfRapCCtLXMsxCs1Ax2Uj/qSytCnolSEXL7LnH80exwHRmBeLtTfGxArsv9Y1NHr24NarfN6H0QxysZ3g==", "dev": true, "dependencies": { "optional": "0.1.4", - "tslib": "2.1.0" + "tslib": "2.2.0" }, "funding": { "type": "opencollective", @@ -2824,6 +2825,12 @@ } } }, + "node_modules/@nestjs/testing/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -2982,6 +2989,41 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@sendgrid/client": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.4.3.tgz", + "integrity": "sha512-tTaHx893w5iqG0sVtUnMyRchuwYF95k4UOkmov1MouMIeMUbNvbalITo7cG7YSXUTY9rT2t4eBY6HcEBCVeqfg==", + "dependencies": { + "@sendgrid/helpers": "^7.4.3", + "axios": "^0.21.1" + }, + "engines": { + "node": "6.* || 8.* || >=10.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.4.3.tgz", + "integrity": "sha512-Wt+68g1sVEM5UspJh34O/cxtv6BBbtAIk7U9B3PB2ySOtPs9e6hI1QkgYVwpNmkt7k2p86muUNyma/Aig25agg==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.4.4.tgz", + "integrity": "sha512-9+dyArajxbPY7eJJAd2eps7MVsXW9E4Y294gWeMYku0/Id/qAivexidjstvkkVlZdlrHbEsGDlnMEcw2eXOiFg==", + "dependencies": { + "@sendgrid/client": "^7.4.3", + "@sendgrid/helpers": "^7.4.3" + }, + "engines": { + "node": "6.* || 8.* || >=10.*" + } + }, "node_modules/@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -6002,7 +6044,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17838,13 +17879,21 @@ } }, "@nestjs/testing": { - "version": "7.6.15", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.15.tgz", - "integrity": "sha512-AWr8stnRoS0yCU/EotAJKveYXm1XSB4ZT+ReqFDXhPyqC1ppOfU+zZuCjQlQHorMzidsVHsAstK5/rUsXnrZUQ==", + "version": "7.6.17", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.17.tgz", + "integrity": "sha512-wWImNvfRapCCtLXMsxCs1Ax2Uj/qSytCnolSEXL7LnH80exwHRmBeLtTfGxArsv9Y1NHr24NarfN6H0QxysZ3g==", "dev": true, "requires": { "optional": "0.1.4", - "tslib": "2.1.0" + "tslib": "2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, "@nodelib/fs.scandir": { @@ -17973,6 +18022,32 @@ "@angular-devkit/schematics": "11.2.6" } }, + "@sendgrid/client": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.4.3.tgz", + "integrity": "sha512-tTaHx893w5iqG0sVtUnMyRchuwYF95k4UOkmov1MouMIeMUbNvbalITo7cG7YSXUTY9rT2t4eBY6HcEBCVeqfg==", + "requires": { + "@sendgrid/helpers": "^7.4.3", + "axios": "^0.21.1" + } + }, + "@sendgrid/helpers": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.4.3.tgz", + "integrity": "sha512-Wt+68g1sVEM5UspJh34O/cxtv6BBbtAIk7U9B3PB2ySOtPs9e6hI1QkgYVwpNmkt7k2p86muUNyma/Aig25agg==", + "requires": { + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.4.4.tgz", + "integrity": "sha512-9+dyArajxbPY7eJJAd2eps7MVsXW9E4Y294gWeMYku0/Id/qAivexidjstvkkVlZdlrHbEsGDlnMEcw2eXOiFg==", + "requires": { + "@sendgrid/client": "^7.4.3", + "@sendgrid/helpers": "^7.4.3" + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -20455,8 +20530,7 @@ "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 + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "defaults": { "version": "1.0.3", diff --git a/package.json b/package.json index d192e10..22dd44d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@nestjs/platform-express": "^7.6.15", "@nestjs/sequelize": "^0.2.0", "@nestjs/swagger": "^4.8.0", + "@sendgrid/mail": "^7.4.4", "apollo-server-express": "^2.23.0", "bcrypt": "^5.0.1", "class-transformer": "0.4.0", @@ -56,7 +57,7 @@ "devDependencies": { "@nestjs/cli": "^7.6.0", "@nestjs/schematics": "^7.3.0", - "@nestjs/testing": "^7.6.15", + "@nestjs/testing": "^7.6.17", "@types/bcrypt": "^3.0.1", "@types/express": "^4.17.11", "@types/jest": "^26.0.23", diff --git a/schema.gql b/schema.gql index a332527..d9d5e18 100644 --- a/schema.gql +++ b/schema.gql @@ -2,48 +2,22 @@ # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) # ------------------------------------------------------ -type Address { - id: Int! - personId: Int! - person: Person! - street: String! - number: String! - zipcode: String! - neighborhood: String! - complement: String - city: String! - state: String! - country: String! - createdAt: DateTime! - updatedAt: DateTime! -} - -""" -A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. -""" -scalar DateTime - -type Contact { - id: Int! - personId: Int! - person: Person! - contactType: String! - value: String! - complement: String - created_at: DateTime! - updated_at: DateTime! -} - type Modules { id: Int! acl: Acl! title: String! router: String! description: String! + submenus: String! createdAt: DateTime! updatedAt: DateTime! } +""" +A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. +""" +scalar DateTime + type Acl { id: Int! roleId: Int! @@ -68,18 +42,34 @@ type Role { updatedAt: DateTime! } -type User { +type Address { id: Int! - personId: Int! - person: Person! - login: String! - rememberToken: String - roles: [Role!]! + userId: Int! + user: User! + street: String! + number: String! + zipcode: String! + neighborhood: String! + complement: String + city: String! + state: String! + country: String! createdAt: DateTime! updatedAt: DateTime! } -type Person { +type Contact { + id: Int! + userId: Int! + user: User! + contactType: String! + value: String! + complement: String + created_at: DateTime! + updated_at: DateTime! +} + +type User { id: Int! name: String! cpf: String! @@ -87,21 +77,23 @@ type Person { birthday: DateTime! gender: String! avatar: String! - createdAt: DateTime! - updatedAt: DateTime! - user: User! + login: String! + rememberToken: String + roles: [Role!]! addresses: [Address!]! contacts: [Contact!]! + createdAt: DateTime! + updatedAt: DateTime! } -type PersonEdge { +type UserEdge { cursor: String! - node: Person! + node: User! } -type PersonResponse { - edges: [PersonEdge!] - nodes: [Person!] +type UsersResponse { + edges: [UserEdge!] + nodes: [User!] totalCount: Int! hasNextPage: Boolean } @@ -119,13 +111,16 @@ type AclResponse { } type Query { - people(paginate: Pagination!): PersonResponse! - person(id: Int!): Person! - acls(paginate: Pagination!): AclResponse! + users(crudList: CrudListInputs!): UsersResponse! + user(id: Int!): User! + acls(crudList: CrudListInputs!): AclResponse! acl(id: Int!): Acl! } -input Pagination { +input CrudListInputs { limit: Int = 10 - page: Int! + page: Int = 1 + field: String = "id" + order: String = "ASC" + filter: String = "" } diff --git a/src/app.module.ts b/src/app.module.ts index 23e7354..1854f21 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,7 +3,6 @@ import { ConfigModule } from '@nestjs/config'; import { SequelizeModule } from '@nestjs/sequelize'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { PeopleModule } from './modules/people/people.module'; import { GraphQLModule } from '@nestjs/graphql'; import { AuthModule } from './modules/auth/auth.module'; import { UsersModule } from './modules/users/users.module'; @@ -31,7 +30,6 @@ const ENV = process.env.NODE_ENV; autoLoadModels: true, synchronize: false, }), - PeopleModule, UsersModule, AuthModule, AclModule, diff --git a/src/helpers/validations/CpfAlreadyExist.validator.ts b/src/helpers/validations/CpfAlreadyExist.validator.ts index 16e339b..41a0af6 100644 --- a/src/helpers/validations/CpfAlreadyExist.validator.ts +++ b/src/helpers/validations/CpfAlreadyExist.validator.ts @@ -7,27 +7,26 @@ import { ValidatorConstraintInterface, } from 'class-validator'; import { Op } from 'sequelize'; - -import { Person } from '../../modules/people/entities/person.entity'; +import { User } from '../../modules/users/entities/user.entity'; @ValidatorConstraint({ async: true }) @Injectable() export class CpfAlreadyExistConstraint implements ValidatorConstraintInterface { constructor( - @Inject('PERSON_REPOSITORY') - private personRepository: typeof Person, + @Inject('USER_REPOSITORY') + private userRepository: typeof User, ) {} async validate(cpf: string, validationArguments: ValidationArguments) { - let { id } = validationArguments.object as Person; + let { id } = validationArguments.object as User; if (!id) { id = 0; } try { - const person = await this.personRepository.findOne({ + const user = await this.userRepository.findOne({ where: { cpf, id: { [Op.not]: id } }, }); - return !!!person; + return !!!user; } catch (e) { return false; } diff --git a/src/helpers/validations/EmailAlreadyExist.validator.ts b/src/helpers/validations/EmailAlreadyExist.validator.ts index 5ff8d1f..7278d86 100644 --- a/src/helpers/validations/EmailAlreadyExist.validator.ts +++ b/src/helpers/validations/EmailAlreadyExist.validator.ts @@ -7,27 +7,27 @@ import { ValidatorConstraintInterface, } from 'class-validator'; import { Op } from 'sequelize'; -import { Person } from '../../modules/people/entities/person.entity'; +import { User } from '../../modules/users/entities/user.entity'; @ValidatorConstraint({ async: true }) @Injectable() export class EmailAlreadyExistConstraint implements ValidatorConstraintInterface { constructor( - @Inject('PERSON_REPOSITORY') - private personRepository: typeof Person, + @Inject('USER_REPOSITORY') + private userRepository: typeof User, ) {} async validate(email: string, validationArguments: ValidationArguments) { try { - let { id } = validationArguments.object as Person; + let { id } = validationArguments.object as User; if (!id) { id = 0; } - const person = await this.personRepository.findOne({ + const user = await this.userRepository.findOne({ where: { email, id: { [Op.not]: id } }, }); - return !!!person; + return !!!user; } catch (e) { return false; } diff --git a/src/helpers/validations/LoginAlreadyExist.validator.ts b/src/helpers/validations/LoginAlreadyExist.validator.ts index c902610..bf10ce4 100644 --- a/src/helpers/validations/LoginAlreadyExist.validator.ts +++ b/src/helpers/validations/LoginAlreadyExist.validator.ts @@ -8,7 +8,6 @@ import { } from 'class-validator'; import { User } from '../../modules/users/entities/user.entity'; import { Op } from 'sequelize'; -import { Person } from 'src/modules/people/entities/person.entity'; @ValidatorConstraint({ async: true }) @Injectable() export class LoginAlreadyExistConstraint @@ -19,14 +18,15 @@ export class LoginAlreadyExistConstraint ) {} async validate(login: string, validationArguments: ValidationArguments) { - let { id } = validationArguments.object as Person; + let { id } = validationArguments.object as User; if (!id) { id = 0; } try { const user = await this.user.findOne({ - where: { login, personId: { [Op.not]: id } }, + where: { login, id: { [Op.not]: id } }, }); + return !!!user; } catch (e) { return false; diff --git a/src/main.ts b/src/main.ts index f6aaec4..6b5be3b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,11 @@ import { CustomExceptionFilter } from './filters/exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.enableCors(); useContainer(app.select(AppModule), { fallbackOnErrors: true }); app.useGlobalPipes(new CustomValidationPipe()); app.useGlobalFilters(new CustomExceptionFilter()); - + app.setGlobalPrefix('v1'); const config = new DocumentBuilder() .setTitle('My Api') .setDescription( @@ -21,7 +22,7 @@ async function bootstrap() { .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api', app, document); + SwaggerModule.setup('v1/swagger', app, document); await app.listen(3000); } diff --git a/src/modules/acl/acl.controller.ts b/src/modules/acl/acl.controller.ts index a4ef00f..8eb52ff 100644 --- a/src/modules/acl/acl.controller.ts +++ b/src/modules/acl/acl.controller.ts @@ -13,8 +13,9 @@ import { ApiBearerAuth, ApiBody, ApiCreatedResponse, + ApiTags, } from '@nestjs/swagger'; -import { HttpResponse } from 'src/utils/http-response'; +import { HttpResponse } from '../../utils/http-response'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { Acl } from '../shared/decorators/acl.decorator'; import { AclGuard } from '../shared/guards/acl.guard'; @@ -25,6 +26,7 @@ import { CreateAclDto } from './dto/create-acl.dto'; @UseGuards(AclGuard) @UseGuards(JwtAuthGuard) @ApiBearerAuth() +@ApiTags('ACL') export class AclController { constructor(private readonly aclService: AclService) {} @@ -35,33 +37,33 @@ export class AclController { status: 422, description: 'Error to create a clt', }) - @Acl('POST_USERS') + @Acl('POST_ACL') async create(@Body() createAclDto: CreateAclDto) { await this.aclService.create(createAclDto); return HttpResponse.created('ACL criada com sucesso!'); } - @Get() - @Acl('GET_USERS') - findAll() { - return this.aclService.findAll({ limit: 0, offset: 10 }); + @Get('/configurations') + @Acl('GET_ACL') + findRoleAndModules() { + return this.aclService.findRoleAndModules(); } @Get(':id') - @Acl('GET_USERS') + @Acl('GET_ACL') findOne(@Param('id') id: string) { return this.aclService.findOne(+id); } @Patch(':id') - @Acl('UPDATE_USERS') + @Acl('UPDATE_ACL') async update(@Param('id') id: string, @Body() updateAclDto: CreateAclDto) { await this.aclService.update(+id, updateAclDto); return HttpResponse.ok('ACL atualizado com sucesso!'); } @Delete(':id') - @Acl('DELETE_USERS') + @Acl('DELETE_ACL') async remove(@Param('id') id: string) { await this.aclService.remove(+id); return HttpResponse.ok('ACL excluida com sucesso!'); diff --git a/src/modules/acl/acl.module.ts b/src/modules/acl/acl.module.ts index ef47c33..0aeaa1c 100644 --- a/src/modules/acl/acl.module.ts +++ b/src/modules/acl/acl.module.ts @@ -5,9 +5,10 @@ import { SequelizeModule } from '@nestjs/sequelize'; import { Acl } from './entities/acl.entity'; import { Modules } from './entities/module.entity'; import { AclResolver } from './acl.resolver'; +import { Role } from './entities/role.entity'; @Module({ - imports: [SequelizeModule.forFeature([Acl, Modules])], + imports: [SequelizeModule.forFeature([Acl, Modules, Role])], controllers: [AclController], providers: [AclService, AclResolver], }) diff --git a/src/modules/acl/acl.resolver.ts b/src/modules/acl/acl.resolver.ts index b083932..1e5cfdc 100644 --- a/src/modules/acl/acl.resolver.ts +++ b/src/modules/acl/acl.resolver.ts @@ -1,16 +1,12 @@ import { Resolver, Query, Args, Int } from '@nestjs/graphql'; - -import { Pagination } from '../shared/interfaces/pagination.input'; -import { - PaginationInputGraphql, - PaginationResolveGraphql, -} from '../shared/interfaces/pagination.interface'; import { GqlAuthGuard } from '../shared/guards/graphql-auth.guard'; import { UseGuards } from '@nestjs/common'; - import AclResponse from './acl.response'; import { Acl } from './entities/acl.entity'; import { AclService } from './acl.service'; +import { CrudList } from '../shared/interfaces/crud-list.interface'; +import { PaginationResolveGraphql } from '../shared/interfaces/pagination.interface'; +import { CrudListInputs } from '../shared/interfaces/crud-list.input'; @Resolver() @UseGuards(GqlAuthGuard) @@ -18,17 +14,29 @@ export class AclResolver { LIMIT_PER_PAGE = 10; constructor(private readonly aclService: AclService) {} + //TODO: make this class extendable @Query(() => AclResponse) async acls( - @Args('paginate', { type: () => Pagination }) - paginate: PaginationInputGraphql, + @Args('crudList', { type: () => CrudListInputs }) + crudList: CrudList, ): Promise> { - const { limit = this.LIMIT_PER_PAGE, page } = paginate; + const { + limit = this.LIMIT_PER_PAGE, + page = 1, + filter = '', + field, + order, + } = crudList; const offset = (page - 1) * limit; - const { count, rows: acls } = await this.aclService.findAll({ - limit, - offset, - }); + console.log(field); + const { count, rows: acls } = await this.aclService.findAll( + { + limit, + offset, + }, + filter, + { field, order }, + ); const totalPage = Math.floor(count / limit); return { totalCount: count, diff --git a/src/modules/acl/acl.service.ts b/src/modules/acl/acl.service.ts index f4f2145..dacd693 100644 --- a/src/modules/acl/acl.service.ts +++ b/src/modules/acl/acl.service.ts @@ -2,14 +2,20 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { Acl } from './entities/acl.entity'; import { CreateAclDto } from './dto/create-acl.dto'; -import { HttpResponse } from 'src/utils/http-response'; import { PaginationDB } from '../shared/interfaces/pagination.interface'; import { Modules } from './entities/module.entity'; -import { Role } from '../users/entities/role.entity'; - +import { Role } from './entities/role.entity'; +import { Op } from 'sequelize'; +import { SortInputGraphql } from '../shared/interfaces/sort.interface'; +import { OrderInputGraphql } from '../shared/interfaces/order.enum'; +import { HttpResponse } from '../../utils/http-response'; @Injectable() export class AclService { - constructor(@InjectModel(Acl) private aclModel: typeof Acl) {} + constructor( + @InjectModel(Acl) private aclModel: typeof Acl, + @InjectModel(Role) private roleModel: typeof Role, + @InjectModel(Modules) private modulesModel: typeof Modules, + ) {} async create(createAclDto: CreateAclDto) { try { await this.aclModel.upsert(createAclDto); @@ -19,21 +25,48 @@ export class AclService { } } - async findAll({ limit, offset }: PaginationDB) { + async findAll( + { limit, offset }: PaginationDB, + filter = '', + { field = 'id', order = OrderInputGraphql.ASC }: SortInputGraphql, + ) { + const filterConditions = + filter !== '' + ? { + [Op.or]: [ + { + '$role.description$': { [Op.like]: `%${filter}%` }, + }, + { + '$module.title$': { [Op.like]: `%${filter}%` }, + }, + ], + } + : {}; return await this.aclModel.findAndCountAll({ distinct: true, include: [Modules, Role], limit, offset, + // @ts-ignore + where: filterConditions, + order: [[field, order]], }); } + async findRoleAndModules() { + const roles = await this.roleModel.findAll(); + const modules = await this.modulesModel.findAll(); + + return { roles, modules }; + } + async findOne(id: number) { const acl = await this.aclModel.findByPk(id); if (!acl) { throw HttpResponse.notFound('Acl não encontrada'); } - return true; + return acl; } async update(id: number, updateAclDto: CreateAclDto) { @@ -57,4 +90,16 @@ export class AclService { throw HttpResponse.unprocessableEntity('Erro ao excluir acl!'); } } + + async getAclPermissions(roleId: number): Promise { + const acls = await this.aclModel.findAll({ + where: { roleId }, + }); + + if (!acls) { + throw HttpResponse.unprocessableEntity('Acls não encontradas!'); + } + + return acls; + } } diff --git a/src/modules/acl/entities/acl.entity.ts b/src/modules/acl/entities/acl.entity.ts index dd33585..a192352 100644 --- a/src/modules/acl/entities/acl.entity.ts +++ b/src/modules/acl/entities/acl.entity.ts @@ -12,7 +12,7 @@ import { BelongsTo, Model, } from 'sequelize-typescript'; -import { Role } from 'src/modules/users/entities/role.entity'; +import { Role } from '../entities/role.entity'; import { Field, Int, ObjectType } from '@nestjs/graphql'; import { Modules } from './module.entity'; import { Acl as IAcl } from '../interfaces/acl.interface'; diff --git a/src/modules/acl/entities/module.entity.ts b/src/modules/acl/entities/module.entity.ts index 56cc19f..cc691d2 100644 --- a/src/modules/acl/entities/module.entity.ts +++ b/src/modules/acl/entities/module.entity.ts @@ -44,6 +44,11 @@ export class Modules extends Model { @Field() description: string; + @AllowNull(true) + @Column(DataType.JSON) + @Field() + submenus: string; + @CreatedAt @Column({ type: DataType.DATE, field: 'created_at' }) @Field() diff --git a/src/modules/users/entities/role.entity.ts b/src/modules/acl/entities/role.entity.ts similarity index 85% rename from src/modules/users/entities/role.entity.ts rename to src/modules/acl/entities/role.entity.ts index 257697f..bad829b 100644 --- a/src/modules/users/entities/role.entity.ts +++ b/src/modules/acl/entities/role.entity.ts @@ -12,10 +12,10 @@ import { BelongsToMany, HasMany, } from 'sequelize-typescript'; -import { RoleUser } from './role-user.entity'; -import { User } from './user.entity'; +import { RoleUser } from '../../users/entities/role-user.entity'; +import { User } from '../../users/entities/user.entity'; import { Field, Int, ObjectType } from '@nestjs/graphql'; -import { Acl } from '../../acl/entities/acl.entity'; +import { Acl } from './acl.entity'; @ObjectType() @Table({ tableName: 'roles' }) diff --git a/src/modules/acl/interfaces/acl.interface.ts b/src/modules/acl/interfaces/acl.interface.ts index 951ddea..d416579 100644 --- a/src/modules/acl/interfaces/acl.interface.ts +++ b/src/modules/acl/interfaces/acl.interface.ts @@ -1,5 +1,5 @@ -import { Role } from 'src/modules/users/interfaces/role.interface'; -import { DefaultDTO } from '../../people/interfaces/default-dto.interface'; +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; +import { Role } from '../../users/interfaces/role.interface'; export interface Acl extends DefaultDTO { id?: number; diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index b5bb213..3dacb6a 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,36 +1,44 @@ -import { Body, Controller, HttpCode, Post } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpCode, + Request, + Post, + UseGuards, +} from '@nestjs/common'; import { LoginUserDto } from '../users/dto/login-user.dto'; import { AuthService } from './auth.service'; -import { - ApiBody, - ApiCreatedResponse, - ApiBadRequestResponse, -} from '@nestjs/swagger'; +import { ApiBody, ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { RequestPasswordDto } from '../users/dto/request-password.dto'; -import { HttpResponse } from 'src/utils/http-response'; +import { HttpResponse } from '../../utils/http-response'; +import { JwtAuthGuard } from './jwt-auth.guard'; @Controller('auth') +@ApiTags('Auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Post() @ApiBody({ type: LoginUserDto }) - @ApiCreatedResponse({ description: 'Create a person' }) - @ApiBadRequestResponse({ - status: 422, - description: 'Error to create a person', - }) + @HttpCode(200) async login(@Body() loginUserDto: LoginUserDto) { return this.authService.login(loginUserDto); } + @Get('user') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + async getUserAuthorization(@Request() { user }) { + delete user.acl; + delete user.iat; + delete user.exp; + return user; + } + + //TODO: Make test @Post('request-password') @ApiBody({ type: LoginUserDto }) - @ApiCreatedResponse({ description: 'Create a person' }) - @ApiBadRequestResponse({ - status: 422, - description: 'Error to create a person', - }) @HttpCode(200) async requestNewPassword(@Body() requestPasswordDto: RequestPasswordDto) { await this.authService.requestPassword(requestPasswordDto); diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 585108c..2346bd1 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { LoginUserDto } from '../users/dto/login-user.dto'; import { UsersService } from '../users/users.service'; import { JwtService } from '@nestjs/jwt'; -import { User } from '../people/interfaces'; import { RequestPasswordDto } from '../users/dto/request-password.dto'; +import EmailController from '../../utils/helpers/mail/controller/email-controller'; @Injectable() export class AuthService { @@ -12,18 +12,25 @@ export class AuthService { private readonly jwtService: JwtService, ) {} async login(userLogin: LoginUserDto) { - const user = (await this.userService.findOne(userLogin)) as User; + const user = await this.userService.findByLogin(userLogin); return { - access_token: this.jwtService.sign({ sub: user.id, ...user }), + access_token: this.jwtService.sign({ id: user.id }), }; } + async requestPassword(requestPassword: RequestPasswordDto) { const user = await this.userService.requestPassword(requestPassword); + if (!user) { return true; } - //TODO: Send email + // const resultEmail = await EmailController.sendEmail({ + // to: 'robertojf95@gmail.com', + // subject: 'Test send email patters', + // message: `teste message ${user.rememberToken}`, + // }); + // console.log(resultEmail); return true; } } diff --git a/src/modules/auth/jwt.strategy.ts b/src/modules/auth/jwt.strategy.ts index e877206..5b59af0 100644 --- a/src/modules/auth/jwt.strategy.ts +++ b/src/modules/auth/jwt.strategy.ts @@ -1,10 +1,11 @@ import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; +import { UsersService } from '../users/users.service'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor() { + constructor(private usersService: UsersService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, @@ -13,6 +14,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: any) { - return { ...payload }; + const user = await this.usersService.getAclPermissionsByUser(payload.id); + return { ...user, ...payload }; } } diff --git a/src/modules/people/dto/create-person.dto.ts b/src/modules/people/dto/create-person.dto.ts deleted file mode 100644 index 699052c..0000000 --- a/src/modules/people/dto/create-person.dto.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - IsEmail, - IsNotEmpty, - IsDateString, - IsEnum, - IsString, - ValidateNested, - IsArray, - ArrayNotEmpty, -} from 'class-validator'; -import { Type } from 'class-transformer'; -import { - CpfAlreadyExist, - EmailAlreadyExist, -} from '../../../helpers/validations'; -import { GENDER } from '../interfaces/gender.enum'; -import { CreateAddressDto } from './create-address.dto'; -import { CreateContactDto } from './create-contact.dto'; -import { CreateUserDto } from '../../users/dto/create-user.dto'; -import { ApiProperty } from '@nestjs/swagger'; -export class CreatePersonDto { - @ApiProperty({ example: 'Julio Eduardo Joaquim Lima' }) - @IsString({ message: 'name:Campo precisa ser de texto.' }) - @IsNotEmpty({ message: 'name:Campo obrigatório.' }) - name: string; - - @ApiProperty({ example: '398.686.904-20' }) - @IsString({ message: 'cpf:Campo precisa ser de texto.' }) - @IsNotEmpty({ message: 'cpf:Campo obrigatório' }) - @CpfAlreadyExist({ message: 'cpf:Esse Cpf já está cadastrado' }) - cpf: string; - - @ApiProperty({ example: 'julio@email.com' }) - @EmailAlreadyExist({ message: 'email:Esse Email já está cadastrado' }) - @IsEmail({}, { message: 'email:E-mail fora do formato.' }) - @IsNotEmpty({ message: 'email:Campo obrigatório.' }) - email: string; - - @ApiProperty({ example: '1990-08-10' }) - @IsDateString({}, { message: 'birthday:Data nascimento fora do formato.' }) - @IsNotEmpty({ message: 'birthday:Campo obrigatório.' }) - birthday: string; - - @ApiProperty({ enum: GENDER, example: 'MALE' }) - @IsString({ message: 'gender:Campo precisa ser de texto.' }) - @IsEnum(GENDER, { - message: `gender:Precisar ser um dos seguites campos ${Object.keys( - GENDER, - )}`, - }) - @IsNotEmpty({ message: 'gender:Campo obrigatório.' }) - gender: GENDER; - - @ApiProperty({ - example: - 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', - }) - @IsString({ message: 'avatar:Campo precisa ser de texto.' }) - @IsNotEmpty({ message: 'avatar:Campo obrigatório.' }) - avatar: string; - - @ApiProperty({ type: CreateUserDto }) - @ValidateNested({ each: true }) - @Type(() => CreateUserDto) - @IsNotEmpty({ message: 'user:Campo obrigatório.' }) - user: CreateUserDto; - - @ApiProperty({ type: [CreateAddressDto] }) - @ValidateNested({ each: true }) - @Type(() => CreateAddressDto) - @ArrayNotEmpty({ message: 'addresses:Campo está vazio.' }) - @IsArray({ message: 'addresses:Campo precisa ser um array' }) - @IsNotEmpty({ message: 'addresses:Campo obrigatório.' }) - addresses: CreateAddressDto[]; - - @ApiProperty({ type: [CreateContactDto] }) - @ValidateNested({ each: true }) - @Type(() => CreateContactDto) - @ArrayNotEmpty({ message: 'contacts:Campo está vazio.' }) - @IsArray({ message: 'contacts:Campo precisa ser um array' }) - @IsNotEmpty({ message: 'contacts:Campo obrigatório.' }) - contacts: CreateContactDto[]; -} diff --git a/src/modules/people/entities/person.entity.ts b/src/modules/people/entities/person.entity.ts deleted file mode 100644 index f54903d..0000000 --- a/src/modules/people/entities/person.entity.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - Model, - AllowNull, - Column, - Table, - DataType, - Unique, - PrimaryKey, - AutoIncrement, - UpdatedAt, - CreatedAt, - HasMany, - HasOne, -} from 'sequelize-typescript'; -import { Address } from './address.entity'; -import { Contact } from './contact.entity'; -import { Person as IPerson, GENDER } from '../interfaces'; -import { User } from '../../users/entities/user.entity'; -import { Field, Int, ObjectType } from '@nestjs/graphql'; -@ObjectType() -@Table({ tableName: 'people' }) -export class Person extends Model { - @PrimaryKey - @AutoIncrement - @AllowNull(false) - @Unique - @Column(DataType.INTEGER) - @Field(() => Int) - id?: number; - - @AllowNull(false) - @Column(DataType.STRING) - @Field() - name: string; - - @Unique - @AllowNull(false) - @Column(DataType.STRING) - @Field() - cpf: string; - - @Unique - @AllowNull(false) - @Column(DataType.STRING) - @Field() - email: string; - - @AllowNull(false) - @Column(DataType.DATEONLY) - @Field() - birthday: Date; - - @AllowNull(false) - @Column({ - type: DataType.ENUM(GENDER.MALE, GENDER.FEMALE, GENDER.OTHER), - }) - @Field() - gender: GENDER; - - @AllowNull(false) - @Column(DataType.STRING) - @Field() - avatar: string; - - @CreatedAt - @Column({ type: DataType.DATE, field: 'created_at' }) - @Field() - createdAt?: Date; - - @UpdatedAt - @Column({ type: DataType.DATE, field: 'updated_at' }) - @Field() - updatedAt?: Date; - - @HasOne(() => User, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Field(() => User) - user?: User; - - @HasMany(() => Address, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Field(() => [Address]) - addresses: Address[]; - - @HasMany(() => Contact, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Field(() => [Contact]) - contacts: Contact[]; -} diff --git a/src/modules/people/interfaces/contact.interface.ts b/src/modules/people/interfaces/contact.interface.ts deleted file mode 100644 index 077b49c..0000000 --- a/src/modules/people/interfaces/contact.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DefaultDTO } from './default-dto.interface'; -import { Person } from './person.interface'; - -export interface Contact extends DefaultDTO { - personId?: number; - person?: Person; - contactType: string; - value: string; - complement?: string; -} diff --git a/src/modules/people/interfaces/index.ts b/src/modules/people/interfaces/index.ts deleted file mode 100644 index 415d608..0000000 --- a/src/modules/people/interfaces/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './address.interface'; -export * from './contact.interface'; -export * from './contact_type.enum'; -export * from './default-dto.interface'; -export * from './gender.enum'; -export * from './person.interface'; -export * from '../../users/interfaces/user.interface'; diff --git a/src/modules/people/interfaces/person.interface.ts b/src/modules/people/interfaces/person.interface.ts deleted file mode 100644 index be7ee3a..0000000 --- a/src/modules/people/interfaces/person.interface.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Address } from './address.interface'; -import { Contact } from './contact.interface'; -import { DefaultDTO } from './default-dto.interface'; -import { GENDER } from './gender.enum'; -import { User } from '../../users/interfaces/user.interface'; - -export interface Person extends DefaultDTO { - name: string; - cpf: string; - email: string; - birthday: string; - gender: GENDER; - avatar: string; - user?: User; - addresses?: Address[]; - contacts?: Contact[]; -} diff --git a/src/modules/people/people.controller.ts b/src/modules/people/people.controller.ts deleted file mode 100644 index 807e38b..0000000 --- a/src/modules/people/people.controller.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - Controller, - Get, - Post, - Body, - Patch, - Param, - Delete, - UseGuards, -} from '@nestjs/common'; -import { PeopleService } from './people.service'; -import { CreatePersonDto } from './dto/create-person.dto'; -import { UpdatePersonDto } from './dto/update-person.dto'; -import { HttpResponse } from '../../utils/http-response'; -import { - ApiBody, - ApiCreatedResponse, - ApiBadRequestResponse, - ApiBearerAuth, -} from '@nestjs/swagger'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; -import { Acl } from '../shared/decorators/acl.decorator'; -import { AclGuard } from '../shared/guards/acl.guard'; -@Controller('people') -@UseGuards(AclGuard) -@UseGuards(JwtAuthGuard) -@ApiBearerAuth() -export class PeopleController { - constructor(private readonly peopleService: PeopleService) {} - - @Post() - @ApiBody({ type: CreatePersonDto }) - @ApiCreatedResponse({ description: 'Create a person' }) - @ApiBadRequestResponse({ - status: 422, - description: 'Error to create a person', - }) - async create(@Body() createPersonDto: CreatePersonDto) { - await this.peopleService.create(createPersonDto); - return HttpResponse.created('Usuário criado com sucesso!'); - } - - @Get() - @Acl('GET_USERS') - findAll() { - return this.peopleService.findAll({ limit: 0, offset: 10 }); - } - - @Get(':id') - @Acl('GET_USERS') - findOne(@Param('id') id: string) { - return this.peopleService.findOne(+id); - } - - @Patch(':id') - async update( - @Param('id') id: string, - @Body() updatePersonDto: UpdatePersonDto, - ) { - await this.peopleService.update(+id, updatePersonDto); - return HttpResponse.ok('Usuário atualizado com sucesso!'); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.peopleService.remove(+id); - } -} diff --git a/src/modules/people/people.module.ts b/src/modules/people/people.module.ts deleted file mode 100644 index 314dcfe..0000000 --- a/src/modules/people/people.module.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Module } from '@nestjs/common'; -import { PeopleService } from './people.service'; -import { PeopleController } from './people.controller'; -import { SequelizeModule } from '@nestjs/sequelize'; -import { User } from '../users/entities/user.entity'; -import { Person } from './entities/person.entity'; -import { Address } from './entities/address.entity'; -import { Contact } from './entities/contact.entity'; -import { personProviders } from '../../providers/person.providers'; -import { - CpfAlreadyExistConstraint, - EmailAlreadyExistConstraint, - LoginAlreadyExistConstraint, -} from '../../helpers/validations'; -import { Role } from '../users/entities/role.entity'; -import { RoleUser } from '../users/entities/role-user.entity'; -import { userProviders } from '../../providers/user.providers'; -import { PeopleResolver } from './people.resolver'; - -@Module({ - imports: [ - SequelizeModule.forFeature([ - User, - Person, - Address, - Contact, - Role, - RoleUser, - ]), - ], - controllers: [PeopleController], - providers: [ - PeopleService, - CpfAlreadyExistConstraint, - EmailAlreadyExistConstraint, - LoginAlreadyExistConstraint, - PeopleResolver, - ...personProviders, - ...userProviders, - ], - exports: [PeopleService], -}) -export class PeopleModule {} diff --git a/src/modules/people/people.resolver.ts b/src/modules/people/people.resolver.ts deleted file mode 100644 index 2bd22a6..0000000 --- a/src/modules/people/people.resolver.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Resolver, Query, Args, Int } from '@nestjs/graphql'; -import { Person } from './entities/person.entity'; -import { PeopleService } from './people.service'; -import { Pagination } from '../shared/interfaces/pagination.input'; -import { - PaginationInputGraphql, - PaginationResolveGraphql, -} from '../shared/interfaces/pagination.interface'; -import PersonResponse from './person.response'; -import { GqlAuthGuard } from '../shared/guards/graphql-auth.guard'; -import { UseGuards } from '@nestjs/common'; - -@Resolver() -@UseGuards(GqlAuthGuard) -export class PeopleResolver { - LIMIT_PER_PAGE = 10; - constructor(private readonly peoplService: PeopleService) {} - - @Query(() => PersonResponse) - async people( - @Args('paginate', { type: () => Pagination }) - paginate: PaginationInputGraphql, - ): Promise> { - const { limit = this.LIMIT_PER_PAGE, page } = paginate; - const offset = (page - 1) * limit; - const { count, rows: people } = await this.peoplService.findAll({ - limit, - offset, - }); - const totalPage = Math.floor(count / limit); - return { - totalCount: count, - nodes: people, - hasNextPage: totalPage > page, - }; - } - - @Query(() => Person) - async person(@Args('id', { type: () => Int }) id: number) { - return this.peoplService.findOne(id); - } -} diff --git a/src/modules/people/people.service.spec.ts b/src/modules/people/people.service.spec.ts deleted file mode 100644 index c3a9822..0000000 --- a/src/modules/people/people.service.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { PeopleService } from './people.service'; -import { getModelToken } from '@nestjs/sequelize'; -import { Person } from './entities/person.entity'; -import { Sequelize } from 'sequelize-typescript'; - -describe('PeopleService', () => { - let service: PeopleService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - PeopleService, - { - provide: getModelToken(Person), - useValue: { - findAll: jest.fn(() => []), - create: jest.fn(() => { - a: '1'; - }), - }, - }, - { - provide: Sequelize, - useValue: { - transaction: jest.fn(() => []), - }, - }, - ], - }).compile(); - - service = module.get(PeopleService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/modules/people/people.service.ts b/src/modules/people/people.service.ts deleted file mode 100644 index 3cdad9c..0000000 --- a/src/modules/people/people.service.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Body, Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/sequelize'; -import { Sequelize } from 'sequelize-typescript'; -import { HttpResponse } from '../../utils/http-response'; -import { PaginationDB } from '../shared/interfaces/pagination.interface'; -import { CreatePersonDto } from './dto/create-person.dto'; -import { UpdatePersonDto } from './dto/update-person.dto'; -import { Address } from './entities/address.entity'; -import { Contact } from './entities/contact.entity'; -import { Person } from './entities/person.entity'; -import { User } from '../users/entities/user.entity'; -import { Contact as IContact } from './interfaces/contact.interface'; - -@Injectable() -export class PeopleService { - constructor( - @InjectModel(Person) private personModel: typeof Person, - @InjectModel(Contact) private contactsModel: typeof Contact, - @InjectModel(Address) private addressesModel: typeof Address, - private sequelize: Sequelize, - ) {} - - async create(@Body() createPersonDto: CreatePersonDto) { - const transaction = await this.sequelize.transaction(); - try { - const person = await this.personModel.create(createPersonDto, { - include: [Address, Contact, User], - transaction, - }); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - person.user.addRoles(createPersonDto.user.roles); - - await transaction.commit(); - - return true; - } catch (error) { - console.log(error); - await transaction.rollback(); - throw HttpResponse.unprocessableEntity('Erro ao salvar usuário!'); - } - } - - async findAll({ limit, offset }: PaginationDB) { - const people = await this.personModel.findAndCountAll({ - include: [Address, Contact, User], - distinct: true, - limit, - offset, - }); - return people; - } - - async findOne(id: number) { - return await this.personModel.findByPk(id, { - include: [Address, Contact, User], - }); - } - - async update(id: number, updatePersonDto: UpdatePersonDto) { - const transaction = await this.sequelize.transaction(); - try { - const person = await this.personModel.findByPk(id, { - include: [Contact, Address, User], - }); - - //TODO: Make this code a new class - - const toSave = []; - - const dtoKeys = Object.keys(updatePersonDto); - const associations = Object.keys(this.personModel.associations); - - dtoKeys.forEach((key) => { - const hasAssociation = associations.includes(key); - if (!hasAssociation) { - return; - } - updatePersonDto[key].forEach(async (contact: IContact) => { - if (contact.id) { - const finded = person[key]?.find(({ id }) => contact.id === id); - finded?.update(contact); - return; - } - toSave.push( - this[`${key}Model`]?.create({ ...contact, personId: person.id }), - ); - }); - }); - - person.update(updatePersonDto); - - await Promise.all(toSave); - await transaction.commit(); - return true; - } catch (error) { - await transaction.rollback(); - throw HttpResponse.unprocessableEntity('Erro ao atualizar usuário!'); - } - } - - remove(id: number) { - return `This action removes a #${id} user`; - } -} diff --git a/src/modules/people/person.response.ts b/src/modules/people/person.response.ts deleted file mode 100644 index 24d850f..0000000 --- a/src/modules/people/person.response.ts +++ /dev/null @@ -1,6 +0,0 @@ -import relayTypes from '../shared/interfaces/relay.types'; -import { ObjectType } from '@nestjs/graphql'; -import { Person } from './entities/person.entity'; - -@ObjectType() -export default class PersonResponse extends relayTypes(Person) {} diff --git a/src/modules/shared/guards/acl.guard.ts b/src/modules/shared/guards/acl.guard.ts index 8ee94db..6faccef 100644 --- a/src/modules/shared/guards/acl.guard.ts +++ b/src/modules/shared/guards/acl.guard.ts @@ -14,8 +14,8 @@ export class AclGuard implements CanActivate { if (!requiredRoles) { return true; } - const request = context.switchToHttp().getRequest(); + const { user } = context.switchToHttp().getRequest(); - return requiredRoles.some((role) => request.user.acl.includes(role)); + return requiredRoles.some((role) => user.acl.includes(role)); } } diff --git a/src/modules/shared/interfaces/crud-list.input.ts b/src/modules/shared/interfaces/crud-list.input.ts new file mode 100644 index 0000000..5ac932c --- /dev/null +++ b/src/modules/shared/interfaces/crud-list.input.ts @@ -0,0 +1,19 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; +import { OrderInputGraphql } from './order.enum'; +@InputType() +export class CrudListInputs { + @Field(() => Int, { defaultValue: 10 }) + limit?: number; + + @Field(() => Int, { defaultValue: 1 }) + page: number; + + @Field({ defaultValue: 'id' }) + field: string; + + @Field({ defaultValue: 'ASC' }) + order: OrderInputGraphql; + + @Field({ defaultValue: '' }) + filter: string; +} diff --git a/src/modules/shared/interfaces/crud-list.interface.ts b/src/modules/shared/interfaces/crud-list.interface.ts new file mode 100644 index 0000000..b33982d --- /dev/null +++ b/src/modules/shared/interfaces/crud-list.interface.ts @@ -0,0 +1,6 @@ +import { PaginationInputGraphql } from './pagination.interface'; +import { SortInputGraphql } from './sort.interface'; + +export interface CrudList extends PaginationInputGraphql, SortInputGraphql { + filter: string; +} diff --git a/src/modules/people/interfaces/default-dto.interface.ts b/src/modules/shared/interfaces/default-dto.interface.ts similarity index 100% rename from src/modules/people/interfaces/default-dto.interface.ts rename to src/modules/shared/interfaces/default-dto.interface.ts diff --git a/src/modules/shared/interfaces/order.enum.ts b/src/modules/shared/interfaces/order.enum.ts new file mode 100644 index 0000000..5b838b1 --- /dev/null +++ b/src/modules/shared/interfaces/order.enum.ts @@ -0,0 +1,4 @@ +export enum OrderInputGraphql { + ASC = 'asc', + DESC = 'desc', +} diff --git a/src/modules/shared/interfaces/pagination.input.ts b/src/modules/shared/interfaces/pagination.input.ts deleted file mode 100644 index 6a5efc8..0000000 --- a/src/modules/shared/interfaces/pagination.input.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Field, InputType, Int } from '@nestjs/graphql'; -@InputType() -export class Pagination { - @Field(() => Int, { defaultValue: 10 }) - limit?: number; - - @Field(() => Int) - page: number; -} diff --git a/src/modules/shared/interfaces/sort.input.ts b/src/modules/shared/interfaces/sort.input.ts new file mode 100644 index 0000000..6a104f8 --- /dev/null +++ b/src/modules/shared/interfaces/sort.input.ts @@ -0,0 +1,11 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { OrderInputGraphql } from './order.enum'; + +@InputType() +export class Sort { + @Field({ defaultValue: 'id' }) + field: string; + + @Field({ defaultValue: 'ASC' }) + order: OrderInputGraphql; +} diff --git a/src/modules/shared/interfaces/sort.interface.ts b/src/modules/shared/interfaces/sort.interface.ts new file mode 100644 index 0000000..3b4fb9a --- /dev/null +++ b/src/modules/shared/interfaces/sort.interface.ts @@ -0,0 +1,6 @@ +import { OrderInputGraphql } from './order.enum'; + +export interface SortInputGraphql { + field: string; + order: OrderInputGraphql; +} diff --git a/src/modules/users/dto/change-password.dto.ts b/src/modules/users/dto/change-password.dto.ts new file mode 100644 index 0000000..7776a0d --- /dev/null +++ b/src/modules/users/dto/change-password.dto.ts @@ -0,0 +1,11 @@ +import { IsNotEmpty } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +export class ChangePasswordDto { + @ApiProperty({ example: '123456' }) + @IsNotEmpty({ message: 'password:Campo obrigatório.' }) + currentPassword: string; + + @ApiProperty({ example: '123456' }) + @IsNotEmpty({ message: 'password:Campo obrigatório.' }) + newPassword: string; +} diff --git a/src/modules/people/dto/create-address.dto.ts b/src/modules/users/dto/create-address.dto.ts similarity index 91% rename from src/modules/people/dto/create-address.dto.ts rename to src/modules/users/dto/create-address.dto.ts index 00959dc..4386197 100644 --- a/src/modules/people/dto/create-address.dto.ts +++ b/src/modules/users/dto/create-address.dto.ts @@ -39,4 +39,12 @@ export class CreateAddressDto { @IsString({ message: 'country:Campo precisa ser de texto.' }) @IsNotEmpty({ message: 'country:Campo obrigatório.' }) country: string; + + @ApiProperty({ required: false }) + @IsOptional() + userId: number; + + @ApiProperty({ required: false }) + @IsOptional() + id: number; } diff --git a/src/modules/people/dto/create-contact.dto.ts b/src/modules/users/dto/create-contact.dto.ts similarity index 85% rename from src/modules/people/dto/create-contact.dto.ts rename to src/modules/users/dto/create-contact.dto.ts index 0705ca8..4bbe744 100644 --- a/src/modules/people/dto/create-contact.dto.ts +++ b/src/modules/users/dto/create-contact.dto.ts @@ -21,4 +21,12 @@ export class CreateContactDto { @ApiProperty({ required: false }) @IsOptional() complement: string; + + @ApiProperty({ required: false }) + @IsOptional() + id: number; + + @ApiProperty({ required: false }) + @IsOptional() + userId: number; } diff --git a/src/modules/users/dto/create-user.dto.ts b/src/modules/users/dto/create-user.dto.ts index 3527117..65f1bcc 100644 --- a/src/modules/users/dto/create-user.dto.ts +++ b/src/modules/users/dto/create-user.dto.ts @@ -1,7 +1,81 @@ -import { IsNotEmpty, MinLength, ArrayNotEmpty, IsArray } from 'class-validator'; -import { LoginAlreadyExist } from '../../../helpers/validations'; +import { + IsEmail, + IsNotEmpty, + IsDateString, + IsEnum, + IsString, + ValidateNested, + IsArray, + ArrayNotEmpty, + MinLength, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { + CpfAlreadyExist, + EmailAlreadyExist, + LoginAlreadyExist, +} from '../../../helpers/validations'; import { ApiProperty } from '@nestjs/swagger'; +import { CreateAddressDto } from './create-address.dto'; +import { CreateContactDto } from './create-contact.dto'; +import { GENDER } from '../interfaces/gender.enum'; export class CreateUserDto { + @ApiProperty({ example: 'Julio Eduardo Joaquim Lima' }) + @IsString({ message: 'name:Campo precisa ser de texto.' }) + @IsNotEmpty({ message: 'name:Campo obrigatório.' }) + name: string; + + @ApiProperty({ example: '398.686.904-20' }) + @IsString({ message: 'cpf:Campo precisa ser de texto.' }) + @IsNotEmpty({ message: 'cpf:Campo obrigatório' }) + @CpfAlreadyExist({ message: 'cpf:Esse Cpf já está cadastrado' }) + cpf: string; + + @ApiProperty({ example: 'julio@email.com' }) + @EmailAlreadyExist({ message: 'email:Esse Email já está cadastrado' }) + @IsEmail({}, { message: 'email:E-mail fora do formato.' }) + @IsNotEmpty({ message: 'email:Campo obrigatório.' }) + email: string; + + @ApiProperty({ example: '1990-08-10' }) + @IsDateString({}, { message: 'birthday:Data nascimento fora do formato.' }) + @IsNotEmpty({ message: 'birthday:Campo obrigatório.' }) + birthday: string; + + @ApiProperty({ enum: GENDER, example: 'MALE' }) + @IsString({ message: 'gender:Campo precisa ser de texto.' }) + @IsEnum(GENDER, { + message: `gender:Precisar ser um dos seguites campos ${Object.keys( + GENDER, + )}`, + }) + @IsNotEmpty({ message: 'gender:Campo obrigatório.' }) + gender: GENDER; + + @ApiProperty({ + example: + 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + }) + @IsString({ message: 'avatar:Campo precisa ser de texto.' }) + @IsNotEmpty({ message: 'avatar:Campo obrigatório.' }) + avatar: string; + + @ApiProperty({ type: [CreateAddressDto] }) + @ValidateNested({ each: true }) + @Type(() => CreateAddressDto) + @ArrayNotEmpty({ message: 'addresses:Campo está vazio.' }) + @IsArray({ message: 'addresses:Campo precisa ser um array' }) + @IsNotEmpty({ message: 'addresses:Campo obrigatório.' }) + addresses: CreateAddressDto[]; + + @ApiProperty({ type: [CreateContactDto] }) + @ValidateNested({ each: true }) + @Type(() => CreateContactDto) + @ArrayNotEmpty({ message: 'contacts:Campo está vazio.' }) + @IsArray({ message: 'contacts:Campo precisa ser um array' }) + @IsNotEmpty({ message: 'contacts:Campo obrigatório.' }) + contacts: CreateContactDto[]; + @ApiProperty({ example: 'julio@email.com' }) @LoginAlreadyExist({ message: 'login:Esse login já está cadastrado' }) @IsNotEmpty({ message: 'login:Campo obrigatório.' }) diff --git a/src/modules/users/dto/request-password.dto.ts b/src/modules/users/dto/request-password.dto.ts index 9c6be5f..7bc0c6f 100644 --- a/src/modules/users/dto/request-password.dto.ts +++ b/src/modules/users/dto/request-password.dto.ts @@ -2,6 +2,6 @@ import { IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class RequestPasswordDto { @ApiProperty({ example: 'julio@email.com' }) - @IsNotEmpty({ message: 'login:Campo obrigatório.' }) - login: string; + @IsNotEmpty({ message: 'email:Campo obrigatório.' }) + email: string; } diff --git a/src/modules/people/dto/update-person.dto.ts b/src/modules/users/dto/update-user.dto.ts similarity index 66% rename from src/modules/people/dto/update-person.dto.ts rename to src/modules/users/dto/update-user.dto.ts index 7114fb2..13ef259 100644 --- a/src/modules/people/dto/update-person.dto.ts +++ b/src/modules/users/dto/update-user.dto.ts @@ -1,9 +1,9 @@ import { PartialType } from '@nestjs/mapped-types'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty } from 'class-validator'; -import { CreatePersonDto } from './create-person.dto'; +import { CreateUserDto } from './create-user.dto'; -export class UpdatePersonDto extends PartialType(CreatePersonDto) { +export class UpdateUserDto extends PartialType(CreateUserDto) { @ApiProperty({ example: '1' }) @IsNotEmpty({ message: 'id:Campo obrigatório' }) id: number; diff --git a/src/modules/people/entities/address.entity.ts b/src/modules/users/entities/address.entity.ts similarity index 84% rename from src/modules/people/entities/address.entity.ts rename to src/modules/users/entities/address.entity.ts index 633f1b8..4c9e667 100644 --- a/src/modules/people/entities/address.entity.ts +++ b/src/modules/users/entities/address.entity.ts @@ -12,9 +12,9 @@ import { BelongsTo, ForeignKey, } from 'sequelize-typescript'; -import { Person } from './person.entity'; -import { Address as IAddress } from '../interfaces'; +import { Address as IAddress } from '../interfaces/address.interface'; import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { User } from './user.entity'; @ObjectType() @Table({ tableName: 'addresses' }) export class Address extends Model { @@ -26,20 +26,20 @@ export class Address extends Model { @Field(() => Int) id: number; - @ForeignKey(() => Person) - @Column({ field: 'person_id' }) + @ForeignKey(() => User) + @Column({ field: 'user_id' }) @Field(() => Int) - personId: number; + userId: number; - @Field(() => Person) - @BelongsTo(() => Person, { + @Field(() => User) + @BelongsTo(() => User, { foreignKey: { - name: 'person_id', + name: 'user_id', }, onDelete: 'cascade', onUpdate: 'cascade', }) - person: Person; + user: User; @AllowNull(false) @Column(DataType.STRING) diff --git a/src/modules/people/entities/contact.entity.ts b/src/modules/users/entities/contact.entity.ts similarity index 82% rename from src/modules/people/entities/contact.entity.ts rename to src/modules/users/entities/contact.entity.ts index bae9134..50a5857 100644 --- a/src/modules/people/entities/contact.entity.ts +++ b/src/modules/users/entities/contact.entity.ts @@ -13,9 +13,9 @@ import { CreatedAt, } from 'sequelize-typescript'; import { CONTACTYPE } from '../interfaces/contact_type.enum'; -import { Person } from './person.entity'; -import { Contact as IContact } from '../interfaces'; +import { Contact as IContact } from '../interfaces/contact.interface'; import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { User } from './user.entity'; @ObjectType() @Table({ tableName: 'contacts' }) @@ -28,20 +28,20 @@ export class Contact extends Model { @Field(() => Int) id: number; - @ForeignKey(() => Person) - @Column({ field: 'person_id' }) + @ForeignKey(() => User) + @Column({ field: 'user_id' }) @Field(() => Int) - personId: number; + userId: number; - @BelongsTo(() => Person, { + @BelongsTo(() => User, { foreignKey: { - name: 'person_id', + name: 'user_id', }, onDelete: 'cascade', onUpdate: 'cascade', }) - @Field(() => Person) - person: Person; + @Field(() => User) + user: User; @AllowNull(false) @Column({ diff --git a/src/modules/users/entities/role-user.entity.ts b/src/modules/users/entities/role-user.entity.ts index 841cebf..938afff 100644 --- a/src/modules/users/entities/role-user.entity.ts +++ b/src/modules/users/entities/role-user.entity.ts @@ -7,7 +7,7 @@ import { CreatedAt, ForeignKey, } from 'sequelize-typescript'; -import { Role } from './role.entity'; +import { Role } from '../../acl/entities/role.entity'; import { User } from './user.entity'; import { Field, Int, ObjectType } from '@nestjs/graphql'; @ObjectType() diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 811256a..e7c902c 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -2,25 +2,27 @@ import { AllowNull, AutoIncrement, BeforeCreate, - BelongsTo, + BeforeUpdate, BelongsToMany, Column, CreatedAt, DataType, - ForeignKey, + HasMany, Model, PrimaryKey, Table, Unique, UpdatedAt, } from 'sequelize-typescript'; -import { Person } from '../../people/entities/person.entity'; import { RoleUser } from './role-user.entity'; -import { Role } from './role.entity'; +import { Role } from '../../acl/entities/role.entity'; import { hash } from 'bcrypt'; import { Exclude } from 'class-transformer'; -import { User as IUser } from '../../people/interfaces'; +import { GENDER } from '../interfaces/gender.enum'; import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { Address } from './address.entity'; +import { Contact } from './contact.entity'; +import { User as IUser } from '../interfaces/user.interface'; @ObjectType() @Table({ tableName: 'users' }) @@ -31,22 +33,41 @@ export class User extends Model { @Unique @Column(DataType.INTEGER) @Field(() => Int) - id: number; + id?: number; - @ForeignKey(() => Person) - @Column({ type: DataType.NUMBER, field: 'person_id' }) - @Field(() => Int) - personId: number; - - @BelongsTo(() => Person, { - foreignKey: { - name: 'personId', - }, - onDelete: 'cascade', - onUpdate: 'cascade', + @AllowNull(false) + @Column(DataType.STRING) + @Field() + name: string; + + @Unique + @AllowNull(false) + @Column(DataType.STRING) + @Field() + cpf: string; + + @Unique + @AllowNull(false) + @Column(DataType.STRING) + @Field() + email: string; + + @AllowNull(false) + @Column(DataType.DATEONLY) + @Field() + birthday: Date; + + @AllowNull(false) + @Column({ + type: DataType.ENUM(GENDER.MALE, GENDER.FEMALE, GENDER.OTHER), }) - @Field(() => Person) - person: Person; + @Field() + gender: GENDER; + + @AllowNull(false) + @Column(DataType.STRING) + @Field() + avatar: string; @AllowNull(false) @Unique @@ -68,6 +89,14 @@ export class User extends Model { @Field(() => [Role]) roles: Role[]; + @HasMany(() => Address, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @Field(() => [Address]) + addresses: Address[]; + + @HasMany(() => Contact, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @Field(() => [Contact]) + contacts: Contact[]; + @CreatedAt @Column({ type: DataType.DATE, field: 'created_at' }) @Field() @@ -79,7 +108,9 @@ export class User extends Model { updatedAt: Date; @BeforeCreate + @BeforeUpdate static async hashPassword(instance: User) { + console.log('user', instance.password); instance.password = await hash(instance.password, 10); } } diff --git a/src/modules/people/people.resolver.spec.ts b/src/modules/users/graphql/users.resolver.spec.ts similarity index 54% rename from src/modules/people/people.resolver.spec.ts rename to src/modules/users/graphql/users.resolver.spec.ts index e4d1c8c..6df41a7 100644 --- a/src/modules/people/people.resolver.spec.ts +++ b/src/modules/users/graphql/users.resolver.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { PeopleResolver } from './people.resolver'; +import { UsersResolver } from './users.resolver'; -describe('PeopleResolver', () => { - let resolver: PeopleResolver; +describe('UsersResolver', () => { + let resolver: UsersResolver; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [PeopleResolver], + providers: [UsersResolver], }).compile(); - resolver = module.get(PeopleResolver); + resolver = module.get(UsersResolver); }); it('should be defined', () => { diff --git a/src/modules/users/graphql/users.resolver.ts b/src/modules/users/graphql/users.resolver.ts new file mode 100644 index 0000000..6ae20d0 --- /dev/null +++ b/src/modules/users/graphql/users.resolver.ts @@ -0,0 +1,51 @@ +import { Resolver, Query, Args, Int } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common'; +import PersonResponse from './users.response'; +import { UsersService } from '../users.service'; +import { User } from '../entities/user.entity'; +import { GqlAuthGuard } from '../../shared/guards/graphql-auth.guard'; +import { CrudListInputs } from '../../shared/interfaces/crud-list.input'; +import { PaginationResolveGraphql } from '../../shared/interfaces/pagination.interface'; +import { CrudList } from '../../shared/interfaces/crud-list.interface'; + +@Resolver() +@UseGuards(GqlAuthGuard) +export class UsersResolver { + LIMIT_PER_PAGE = 10; + constructor(private readonly userService: UsersService) {} + + //TODO: make this class extendable + @Query(() => PersonResponse) + async users( + @Args('crudList', { type: () => CrudListInputs }) + crudList: CrudList, + ): Promise> { + const { + limit = this.LIMIT_PER_PAGE, + page = 1, + filter = '', + field, + order, + } = crudList; + const offset = (page - 1) * limit; + + const { count, rows: acls } = await this.userService.findAll( + { + limit, + offset, + }, + filter, + { field, order }, + ); + const totalPage = Math.floor(count / limit); + return { + totalCount: count, + nodes: acls, + hasNextPage: totalPage > page, + }; + } + @Query(() => User) + async user(@Args('id', { type: () => Int }) id: number) { + return this.userService.findOne(id); + } +} diff --git a/src/modules/users/graphql/users.response.ts b/src/modules/users/graphql/users.response.ts new file mode 100644 index 0000000..bee0a55 --- /dev/null +++ b/src/modules/users/graphql/users.response.ts @@ -0,0 +1,6 @@ +import { ObjectType } from '@nestjs/graphql'; +import relayTypes from '../../shared/interfaces/relay.types'; +import { User } from '../entities/user.entity'; + +@ObjectType() +export default class UsersResponse extends relayTypes(User) {} diff --git a/src/modules/people/interfaces/address.interface.ts b/src/modules/users/interfaces/address.interface.ts similarity index 57% rename from src/modules/people/interfaces/address.interface.ts rename to src/modules/users/interfaces/address.interface.ts index 1e28c6a..df3c3e1 100644 --- a/src/modules/people/interfaces/address.interface.ts +++ b/src/modules/users/interfaces/address.interface.ts @@ -1,9 +1,8 @@ -import { DefaultDTO } from './default-dto.interface'; -import { Person } from './person.interface'; - +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; +import { User } from './user.interface'; export interface Address extends DefaultDTO { - personId?: number; - person?: Person; + userId?: number; + user?: User; zipcode: string; street: string; number: string; diff --git a/src/modules/users/interfaces/contact.interface.ts b/src/modules/users/interfaces/contact.interface.ts new file mode 100644 index 0000000..276fbd0 --- /dev/null +++ b/src/modules/users/interfaces/contact.interface.ts @@ -0,0 +1,10 @@ +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; +import { User } from './user.interface'; + +export interface Contact extends DefaultDTO { + userId?: number; + user?: User; + contactType: string; + value: string; + complement?: string; +} diff --git a/src/modules/people/interfaces/contact_type.enum.ts b/src/modules/users/interfaces/contact_type.enum.ts similarity index 100% rename from src/modules/people/interfaces/contact_type.enum.ts rename to src/modules/users/interfaces/contact_type.enum.ts diff --git a/src/modules/people/interfaces/gender.enum.ts b/src/modules/users/interfaces/gender.enum.ts similarity index 100% rename from src/modules/people/interfaces/gender.enum.ts rename to src/modules/users/interfaces/gender.enum.ts diff --git a/src/modules/users/interfaces/role-user.interface.ts b/src/modules/users/interfaces/role-user.interface.ts index 9c8469b..e880d2f 100644 --- a/src/modules/users/interfaces/role-user.interface.ts +++ b/src/modules/users/interfaces/role-user.interface.ts @@ -1,4 +1,4 @@ -import { DefaultDTO } from '../../people/interfaces/default-dto.interface'; +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; export interface RoleUser extends DefaultDTO { userId: number; diff --git a/src/modules/users/interfaces/role.interface.ts b/src/modules/users/interfaces/role.interface.ts index ba9dcfa..8edaf9d 100644 --- a/src/modules/users/interfaces/role.interface.ts +++ b/src/modules/users/interfaces/role.interface.ts @@ -1,5 +1,5 @@ -import { Acl } from 'src/modules/acl/interfaces/acl.interface'; -import { DefaultDTO } from '../../people/interfaces/default-dto.interface'; +import { Acl } from '../../acl/interfaces/acl.interface'; +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; import { RoleUser } from './role-user.interface'; export interface Role extends DefaultDTO { diff --git a/src/modules/users/interfaces/user.interface.ts b/src/modules/users/interfaces/user.interface.ts index 1c3fc9f..878a231 100644 --- a/src/modules/users/interfaces/user.interface.ts +++ b/src/modules/users/interfaces/user.interface.ts @@ -1,9 +1,17 @@ -import { DefaultDTO } from '../../people/interfaces/default-dto.interface'; -import { Person } from '../../people/interfaces/person.interface'; +import { Address } from './address.interface'; +import { Contact } from './contact.interface'; +import { DefaultDTO } from '../../shared/interfaces/default-dto.interface'; +import { GENDER } from './gender.enum'; export interface User extends DefaultDTO { - personId?: number; - person?: Person; + name: string; + cpf: string; + email: string; + birthday: string; + gender: GENDER; + avatar: string; + addresses?: Address[]; + contacts?: Contact[]; login: string; password?: string; rememberToken?: string; diff --git a/src/modules/people/people.controller.spec.ts b/src/modules/users/users.controller.spec.ts similarity index 59% rename from src/modules/people/people.controller.spec.ts rename to src/modules/users/users.controller.spec.ts index 0620e2f..c7b312b 100644 --- a/src/modules/people/people.controller.spec.ts +++ b/src/modules/users/users.controller.spec.ts @@ -1,17 +1,16 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { PeopleController } from './people.controller'; -import { PeopleService } from './people.service'; - -describe('PeopleController', () => { - let controller: PeopleController; +import { UserController } from './users.controller'; +import { UsersService } from './users.service'; +describe('UserController', () => { + let controller: UserController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], - controllers: [PeopleController], + controllers: [UserController], providers: [ { - provide: PeopleService, + provide: UsersService, useValue: { getCats: jest.fn(() => []), addCat: jest.fn(() => { @@ -22,7 +21,7 @@ describe('PeopleController', () => { ], }).compile(); - controller = module.get(PeopleController); + controller = module.get(UserController); }); it('should be defined', () => { diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts new file mode 100644 index 0000000..c0bdd1f --- /dev/null +++ b/src/modules/users/users.controller.ts @@ -0,0 +1,88 @@ +import { + Controller, + Post, + Body, + Patch, + Param, + UseGuards, + Delete, +} from '@nestjs/common'; +import { HttpResponse } from '../../utils/http-response'; +import { ApiBody, ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { Acl } from '../shared/decorators/acl.decorator'; +import { AclGuard } from '../shared/guards/acl.guard'; +import { UsersService } from './users.service'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { ChangePasswordDto } from './dto/change-password.dto'; +@Controller('users') +@UseGuards(AclGuard) +@UseGuards(JwtAuthGuard) +@ApiBearerAuth() +@ApiTags('User') +export class UserController { + constructor(private readonly userService: UsersService) {} + + @Post() + @Acl('POST_USERS') + @ApiBody({ type: CreateUserDto }) + async create(@Body() createUserDto: CreateUserDto) { + await this.userService.create(createUserDto); + return HttpResponse.created('Usuário criado com sucesso!'); + } + + @Patch(':id') + @Acl('UPDATE_USERS') + async update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { + await this.userService.update(+id, updateUserDto); + return HttpResponse.ok('Usuário atualizado com sucesso!'); + } + + @Patch('/addresses/:userId') + @Acl('UPDATE_USERS') + async updateAddresses( + @Param('userId') id: string, + @Body() updateUserDto: UpdateUserDto, + ) { + const addresses = await this.userService.updateAddresses( + +id, + updateUserDto, + ); + return HttpResponse.ok('Usuário atualizado com sucesso!', addresses); + } + + @Delete('/addresses/:addressId') + @Acl('UPDATE_USERS') + async deleteAddress(@Param('addressId') id: string) { + await this.userService.removeAddress(+id); + return HttpResponse.ok('Endereço removido com sucesso!'); + } + + @Patch('/contacts/:userId') + @Acl('UPDATE_USERS') + async updateContacts( + @Param('userId') id: string, + @Body() updateUserDto: UpdateUserDto, + ) { + const contacts = await this.userService.updateContacts(+id, updateUserDto); + return HttpResponse.ok('Usuário atualizado com sucesso!', contacts); + } + + @Delete('/contacts/:contactId') + @Acl('UPDATE_USERS') + async deleteContact(@Param('contactId') id: string) { + await this.userService.removeContact(+id); + return HttpResponse.ok('Contato removido com sucesso!'); + } + + @Patch('/change-password/:id') + @Acl('UPDATE_USERS') + async changePassword( + @Param('id') id: string, + @Body() passwords: ChangePasswordDto, + ) { + await this.userService.changePassword(+id, passwords); + return HttpResponse.ok('A sua senha foi alterada com sucesso!'); + } +} diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 123d258..09c9b0d 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -1,25 +1,34 @@ import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; -import { Address } from '../people/entities/address.entity'; -import { Contact } from '../people/entities/contact.entity'; -import { Person } from '../people/entities/person.entity'; import { RoleUser } from './entities/role-user.entity'; -import { Role } from './entities/role.entity'; +import { Role } from '../acl/entities/role.entity'; import { User } from './entities/user.entity'; import { UsersService } from './users.service'; +import { UsersResolver } from './graphql/users.resolver'; + +import { Address } from './entities/address.entity'; +import { Contact } from './entities/contact.entity'; +import { UserController } from './users.controller'; +import { + CpfAlreadyExistConstraint, + EmailAlreadyExistConstraint, + LoginAlreadyExistConstraint, +} from '../../helpers/validations'; +import { userProviders } from '../../providers/user.providers'; @Module({ imports: [ - SequelizeModule.forFeature([ - User, - Person, - Address, - Contact, - Role, - RoleUser, - ]), + SequelizeModule.forFeature([User, Address, Contact, Role, RoleUser]), + ], + controllers: [UserController], + providers: [ + UsersService, + CpfAlreadyExistConstraint, + EmailAlreadyExistConstraint, + LoginAlreadyExistConstraint, + UsersResolver, + ...userProviders, ], - providers: [UsersService], exports: [UsersService], }) export class UsersModule {} diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 863707f..f6b4e32 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -1,24 +1,115 @@ -import { Injectable } from '@nestjs/common'; +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { Body, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { LoginUserDto } from './dto/login-user.dto'; import { User } from './entities/user.entity'; import { compareSync } from 'bcrypt'; -import { HttpResponse } from 'src/utils/http-response'; -import { User as IUser } from '../users/interfaces/user.interface'; -import { Role } from './entities/role.entity'; -import { Acl } from '../acl/entities/acl.entity'; -import { Modules } from '../acl/entities/module.entity'; -import { Person } from '../people/entities/person.entity'; +import { HttpResponse } from '../../utils/http-response'; import { randomBytes } from 'crypto'; import { promisify } from 'util'; import { RequestPasswordDto } from './dto/request-password.dto'; +import { Contact } from './entities/contact.entity'; +import { Address } from './entities/address.entity'; +import { Op } from 'sequelize'; +import { Sequelize } from 'sequelize-typescript'; +import { CreateUserDto } from './dto/create-user.dto'; +import { PaginationDB } from '../shared/interfaces/pagination.interface'; +import { OrderInputGraphql } from '../shared/interfaces/order.enum'; +import { SortInputGraphql } from '../shared/interfaces/sort.interface'; +import { Role } from '../acl/entities/role.entity'; +import { Acl } from '../acl/entities/acl.entity'; +import { Modules } from '../acl/entities/module.entity'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { ChangePasswordDto } from './dto/change-password.dto'; @Injectable() export class UsersService { - constructor(@InjectModel(User) private userModel: typeof User) {} - async findOne(userLogin: LoginUserDto) { + constructor( + @InjectModel(User) private userModel: typeof User, + @InjectModel(Contact) private contactsModel: typeof Contact, + @InjectModel(Address) private addressesModel: typeof Address, + private sequelize: Sequelize, + ) {} + + async create(@Body() createUserDto: CreateUserDto) { + const transaction = await this.sequelize.transaction(); + try { + const user = await this.userModel.create(createUserDto, { + include: [Address, Contact], + transaction, + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + user.addRoles(createUserDto.roles); + + await transaction.commit(); + + return true; + } catch (error) { + console.log(error); + await transaction.rollback(); + throw HttpResponse.unprocessableEntity('Erro ao salvar usuário!'); + } + } + async findAll( + { limit, offset }: PaginationDB, + filter = '', + { field = 'id', order = OrderInputGraphql.ASC }: SortInputGraphql, + ) { + const filterConditions = + filter !== '' + ? { + [Op.or]: [ + { + name: { [Op.like]: `%${filter}%` }, + }, + { + cpf: { [Op.like]: `%${filter}%` }, + }, + { + email: { [Op.like]: `%${filter}%` }, + }, + { + '$user.login$': { [Op.like]: `%${filter}%` }, + }, + ], + } + : {}; + return await this.userModel.findAndCountAll({ + distinct: true, + limit, + offset, + // @ts-ignore + where: filterConditions, + order: [[field, order]], + }); + } + + async findByLogin(userLogin: LoginUserDto): Promise { const { login, password } = userLogin; const user = await this.userModel.findOne({ where: { login }, + attributes: ['id', 'password'], + }); + if (!user || !compareSync(password, user.password)) { + throw HttpResponse.badRequest('Dados incorretos!'); + } + return user; + } + + async findOne(id: number): Promise { + const user = await this.userModel.findByPk(id, { + include: [Address, Contact, Role], + }); + if (!user) { + throw HttpResponse.notFound('Usuário não encontrada'); + } + return user; + } + + async getAclPermissionsByUser(userId: number) { + const user = await this.userModel.findOne({ + where: { id: userId }, include: [ { model: Role, @@ -26,23 +117,26 @@ export class UsersService { include: [ { model: Acl, - include: [Modules], + include: [ + { + model: Modules, + attributes: ['description', 'title', 'router', 'submenus'], + }, + ], attributes: ['isShow', 'isGet', 'isPost', 'isUpdate', 'isDelete'], }, ], }, ], + attributes: ['id', 'name', 'avatar', 'cpf', 'birthday', 'gender'], }); - if (!user || !compareSync(password, user.password)) { - throw HttpResponse.badRequest('Dados incorretos!'); + if (!user) { + throw HttpResponse.notFound('Usuário não encontrada'); } - const userDecode = user.toJSON() as IUser; - //TODO: Fix exclude field on User Entity - delete userDecode.password; + const decodedUser = user.toJSON() as User; let acl = []; let modules = []; - //TODO: Refactory!!! - userDecode.roles.map((role: any) => { + for (const role of decodedUser.roles) { const aclToGuard = role.acl .reduce((acc, current) => { const keys = Object.keys(current); @@ -51,31 +145,28 @@ export class UsersService { .split(','); arrays.pop(); acc = [...arrays, ...acc]; - delete current.module.createdAt; - delete current.module.updatedAt; - delete current.module.description; modules = [...modules, current.module]; return acc; }, []) .map((acl: string) => acl.toUpperCase().replace('IS', '')); acl = [...acl, ...aclToGuard]; - - delete role.RoleUser; - delete role.acl; - return role; - }); + } return { - acl: [...new Set(acl)], - ...userDecode, + id: userId, + name: decodedUser.name, + avatar: decodedUser.avatar, + cpf: decodedUser.cpf, + birthday: decodedUser.birthday, + gender: decodedUser.gender, modules: this.removeDuplicateModule(modules), + acl: [...new Set(acl)], }; } - async requestPassword({ login }: RequestPasswordDto) { + async requestPassword({ email }: RequestPasswordDto) { const user = await this.userModel.findOne({ - where: { login }, - include: [Person], + where: { email }, }); if (!user) { return null; @@ -85,7 +176,136 @@ export class UsersService { await user.update({ rememberToken }); - return { email: user.person.email, rememberToken }; + return { email: user.email, rememberToken }; + } + + async update(id: number, updatePersonDto: UpdateUserDto) { + const transaction = await this.sequelize.transaction(); + try { + const user = await this.userModel.findByPk(id, { + transaction, + }); + + user.update(updatePersonDto); + + await transaction.commit(); + + return true; + } catch (error) { + console.log(error); + await transaction.rollback(); + throw HttpResponse.unprocessableEntity('Erro ao salvar usuário!'); + } + } + + async updateAddresses(id: number, updatePersonDto: UpdateUserDto) { + const transaction = await this.sequelize.transaction(); + try { + const addresses = updatePersonDto.addresses.map((address) => { + if (!address.userId) { + address.userId = id; + } + return address; + }); + + await this.addressesModel.bulkCreate(addresses, { + updateOnDuplicate: [ + 'street', + 'zipcode', + 'neighborhood', + 'complement', + 'city', + 'state', + 'country', + 'updatedAt', + ], + transaction, + }); + + await transaction.commit(); + //TODO: Maybe find another way to get ids from bulkCreate + const updatedAddresses = await this.addressesModel.findAll({ + where: { userId: id }, + }); + + return updatedAddresses; + } catch (error) { + console.log(error); + await transaction.rollback(); + throw HttpResponse.unprocessableEntity('Erro ao salvar endereços!'); + } + } + + async updateContacts(id: number, updatePersonDto: UpdateUserDto) { + const transaction = await this.sequelize.transaction(); + try { + const contacts = updatePersonDto.contacts.map((contact) => { + if (!contact.userId) { + contact.userId = id; + } + return contact; + }); + + await this.contactsModel.bulkCreate(contacts, { + updateOnDuplicate: ['contactType', 'value', 'updatedAt'], + transaction, + returning: true, + hooks: true, + }); + + await transaction.commit(); + //TODO: Maybe find another way to get ids from bulkCreate + const updatedContacts = await this.contactsModel.findAll({ + where: { userId: id }, + }); + + return updatedContacts; + } catch (error) { + console.log(error); + await transaction.rollback(); + throw HttpResponse.unprocessableEntity('Erro ao salvar contatos!'); + } + } + async changePassword(id: number, passwords: ChangePasswordDto) { + const { currentPassword, newPassword } = passwords; + try { + const user = await this.userModel.findOne({ + where: { id }, + attributes: ['id', 'password'], + }); + + if (!user || !compareSync(currentPassword, user.password)) { + throw HttpResponse.badRequest('Senha atual incorreta!'); + } + + await user.update({ password: newPassword }); + return true; + } catch (error) { + console.log(error); + throw HttpResponse.unprocessableEntity('Erro ao salvar nova senha!'); + } + } + + async removeAddress(id: number) { + try { + const address = await this.addressesModel.findByPk(id); + await address.destroy(); + return true; + } catch (error) { + console.log(error); + throw HttpResponse.unprocessableEntity('Erro ao excluir endereço!'); + } + } + + async removeContact(id: number) { + try { + const contact = await this.contactsModel.findByPk(id); + await contact.destroy(); + return true; + } catch (error) { + console.log(error); + throw HttpResponse.unprocessableEntity('Erro ao excluir contato!'); + } } private removeDuplicateModule(duplicates): Array { diff --git a/src/pipes/custom-validation.pipe.ts b/src/pipes/custom-validation.pipe.ts index 03fc7e1..3ddb6a0 100644 --- a/src/pipes/custom-validation.pipe.ts +++ b/src/pipes/custom-validation.pipe.ts @@ -21,10 +21,9 @@ export class CustomValidationPipe extends ValidationPipe { } throw new UnprocessableEntityException( - HttpResponse.unprocessableEntity( - 'Não foi possivel validar.', + HttpResponse.unprocessableEntity('Não foi possivel validar.', [ validations, - ), + ]), ); } } diff --git a/src/providers/person.providers.ts b/src/providers/person.providers.ts deleted file mode 100644 index 027845e..0000000 --- a/src/providers/person.providers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Person } from '../modules/people/entities/person.entity'; - -export const personProviders = [ - { - provide: 'PERSON_REPOSITORY', - useValue: Person, - }, -]; diff --git a/src/utils/helpers/mail/controller/email-controller.ts b/src/utils/helpers/mail/controller/email-controller.ts new file mode 100644 index 0000000..92a4ec3 --- /dev/null +++ b/src/utils/helpers/mail/controller/email-controller.ts @@ -0,0 +1,12 @@ +import Email from '../core/entity/email'; +import EmailRepositorySendGrid from '../core/repository/email-repository-send-grid'; +import SendEmail from '../core/usecase/send-email'; + +export default class EmailController { + static async sendEmail(email: Email) { + const emailRepository = new EmailRepositorySendGrid(); + const sendEmail = new SendEmail(emailRepository); + const resultEmail = await sendEmail.execute(email); + return resultEmail; + } +} diff --git a/src/utils/helpers/mail/core/entity/email.ts b/src/utils/helpers/mail/core/entity/email.ts new file mode 100644 index 0000000..ef29289 --- /dev/null +++ b/src/utils/helpers/mail/core/entity/email.ts @@ -0,0 +1,5 @@ +export default interface Email { + to: string; + subject: string; + message: string; +} diff --git a/src/utils/helpers/mail/core/repository/email-repository-send-grid.ts b/src/utils/helpers/mail/core/repository/email-repository-send-grid.ts new file mode 100644 index 0000000..ff4d83a --- /dev/null +++ b/src/utils/helpers/mail/core/repository/email-repository-send-grid.ts @@ -0,0 +1,20 @@ +import Mail from '../entity/email'; +import EmailRepository from './interfaces/email-repository'; +import { setApiKey, send } from '@sendgrid/mail'; + +export default class EmailRepositorySendGrid implements EmailRepository { + constructor() { + setApiKey(process.env.SENDGRID_API_KEY); + } + async sendEmail({ to, message: text, subject }: Mail): Promise { + const emailOptions = { + from: process.env.EMAIL_FROM, + to, + subject, + text, + html: `${text}`, + }; + const emailResponse = await send(emailOptions); + return emailResponse; + } +} diff --git a/src/utils/helpers/mail/core/repository/interfaces/email-repository.ts b/src/utils/helpers/mail/core/repository/interfaces/email-repository.ts new file mode 100644 index 0000000..ade9bc7 --- /dev/null +++ b/src/utils/helpers/mail/core/repository/interfaces/email-repository.ts @@ -0,0 +1,5 @@ +import Mail from '../../entity/email'; + +export default interface EmailRepository { + sendEmail(mail: Mail): Promise; +} diff --git a/src/utils/helpers/mail/core/usecase/send-email.ts b/src/utils/helpers/mail/core/usecase/send-email.ts new file mode 100644 index 0000000..10b063c --- /dev/null +++ b/src/utils/helpers/mail/core/usecase/send-email.ts @@ -0,0 +1,15 @@ +import Mail from '../entity/email'; +import MailRepository from '../repository/interfaces/email-repository'; + +export default class SendEmail { + mailRepository: MailRepository; + + constructor(mailRepository: MailRepository) { + this.mailRepository = mailRepository; + } + + execute(mail: Mail) { + const resultEmail = this.mailRepository.sendEmail(mail); + return resultEmail; + } +} diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec123.ts similarity index 100% rename from test/app.e2e-spec.ts rename to test/app.e2e-spec123.ts diff --git a/test/auth.e2e-spec.ts b/test/auth.e2e-spec.ts new file mode 100644 index 0000000..eba8036 --- /dev/null +++ b/test/auth.e2e-spec.ts @@ -0,0 +1,130 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExecutionContext, INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from '../src/app.module'; +import { CustomValidationPipe } from '../src/pipes/custom-validation.pipe'; +import { useContainer } from 'class-validator'; +import { Sequelize } from 'sequelize-typescript'; +import { JwtAuthGuard } from '../src/modules/auth/jwt-auth.guard'; +import { CustomExceptionFilter } from '../src/filters/exception.filter'; + +describe('AuthController (e2e)', () => { + let app: INestApplication; + let dbConnection: Sequelize; + const fakeUser = { + id: 1, + name: 'Admin 1', + avatar: + 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', + modules: [ + { + description: 'ACL', + title: 'ACL', + router: '/acl', + submenus: { + menus: [ + { + title: 'Criar', + router: 'register', + }, + { + title: 'List', + router: 'list', + }, + ], + }, + }, + { + description: 'users', + title: 'Usuários', + router: '/users', + submenus: { + menus: [ + { + title: 'Criar', + router: 'register', + }, + { + title: 'List', + router: 'list', + }, + ], + }, + }, + { + description: 'dashboard', + title: 'Dashboard', + router: '/dashboard', + submenus: null, + }, + ], + }; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + providers: [CustomValidationPipe], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ + canActivate: (context: ExecutionContext) => { + const req = context.switchToHttp().getRequest(); + if (req.headers.authorization !== 'Bearer valid') { + return false; + } + req.user = fakeUser; + return true; + }, + }) + .compile(); + + app = moduleFixture.createNestApplication(); + useContainer(app.select(AppModule), { fallbackOnErrors: true }); + app.useGlobalPipes(new CustomValidationPipe()); + app.useGlobalFilters(new CustomExceptionFilter()); + await app.init(); + + dbConnection = moduleFixture.get(Sequelize); + }); + afterEach(async () => { + await app.close(); + }); + + it('/v1/auth (POST)', () => { + const credentials = { + login: 'admin@admin.com', + password: '123456', + }; + return request(app.getHttpServer()) + .post('/auth') + .send(credentials) + .set('Content-Type', 'application/json') + .expect(200); + }); + + it('/v1/auth (POST) - Invalid credntials', () => { + const credentials = { + login: 'invalid@admin.com', + password: 'invalid', + }; + return request(app.getHttpServer()) + .post('/auth') + .send(credentials) + .set('Content-Type', 'application/json') + .expect(400) + .expect({ + code: 400, + message: 'Dados incorretos!', + data: {}, + }); + }); + + it('/v1/auth/user (GET)', () => { + return request(app.getHttpServer()) + .get('/auth/user') + .set('Authorization', `Bearer valid`) + .set('Content-Type', 'application/json') + .expect(200) + .expect(fakeUser); + }); +}); diff --git a/test/user.e2e-spec.ts b/test/user.e2e-spec.ts deleted file mode 100644 index 319690b..0000000 --- a/test/user.e2e-spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; -import { CustomValidationPipe } from './../src/pipes/custom-validation.pipe'; -import { useContainer } from 'class-validator'; -import { Sequelize } from 'sequelize-typescript'; - -describe('UserController (e2e)', () => { - let app: INestApplication; - let dbConnection: Sequelize; - const truncateUser = `DELETE FROM people WHERE (cpf = '837.244.851-58');`; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - providers: [CustomValidationPipe], - }).compile(); - - app = moduleFixture.createNestApplication(); - useContainer(app.select(AppModule), { fallbackOnErrors: true }); - app.useGlobalPipes(new CustomValidationPipe()); - await app.init(); - - dbConnection = moduleFixture.get(Sequelize); - }); - afterEach(async () => { - await app.close(); - }); - - it('/users (POST) - should create a new user', async (done) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - await dbConnection.query(truncateUser); - - const user = { - name: 'Admin 1', - cpf: '837.244.851-58', - email: 'admin@mail1.com', - birthday: '1970-08-09', - gender: 'MALE', - avatar: - 'https://thumbs.dreamstime.com/b/opte-pelo-avatar-placeholder-da-foto-%C3%ADcone-do-perfil-124557887.jpg', - addresses: [ - { - zipcode: '08270-001', - street: 'Avenida Afonso de Sampaio e Sousa', - number: '302', - neighborhood: 'Itaquera', - city: 'São Paulo', - state: 'SP', - country: 'BRAZIL', - }, - ], - contacts: [ - { - contactType: 'CELULAR', - value: '(34) 992-150-179', - }, - ], - user: { - login: 'admin@mail.com', - password: '123456', - roles: [1], - }, - }; - const userResponse = { - code: 201, - message: 'Usuário criado com sucesso!', - data: {}, - }; - - await request(app.getHttpServer()) - .post('/users') - .send(user) - .set('Content-Type', 'application/json') - .expect(userResponse) - .expect(201); - done(); - }); - - it('/users (POST) - should return error with validates messages when pass empty body', () => { - const validationMessages = { - code: 422, - message: 'Não foi possivel validar.', - data: { - name: 'Campo precisa ser de texto.', - cpf: 'Esse Cpf já está cadastrado', - email: 'Esse Email já está cadastrado', - birthday: 'Data nascimento fora do formato.', - gender: 'Campo precisa ser de texto.', - avatar: 'Campo precisa ser de texto.', - user: 'Campo obrigatório.', - addresses: 'Campo está vazio.', - contacts: 'Campo está vazio.', - }, - }; - return request(app.getHttpServer()) - .post('/users') - .set('Content-Type', 'application/json') - .expect(422) - .expect(validationMessages); - }); -});