Compare commits

...
508 Commits
Author SHA1 Message Date
Gabriel Amorim 8551239038 Merge pull request #137 from dadosfera/fix/update-user-email
FIX: update user email
2022-09-14 11:32:58 -03:00
Gabriel Rosa 4cf7c6e17b FIX: update user email 2022-09-14 10:18:03 -03:00
Gabriel Amorim 88c9c36eb0 Merge pull request #136 from dadosfera/fix/connections-migration
Fix: connections migration
2022-09-13 08:58:29 -03:00
Gabriel Rosa 4b317778f7 FIX: connections migration agora vai 2022-09-12 20:13:12 -03:00
Gabriel Rosa 2eea9787cd FIX: connections migration por favor funciona 2022-09-12 19:52:38 -03:00
Gabriel Rosa c1ab5d152f FIX: reseting productConnectionsMissing and productNetworkConfigsMissing 2022-09-12 18:58:24 -03:00
Gabriel Rosa 8798f5d54e FIX: DEBUG_ConnectionsMigrations - customer_connections 2022-09-12 18:47:42 -03:00
Gabriel Amorim 1da733b5bc Merge pull request #135 from dadosfera/fix/connections-migration
FIX: DEBUG-ConnectionsMigrations
2022-09-12 17:17:54 -03:00
Gabriel Rosa 930c8bc037 FIX: DEBUG-ConnectionsMigrations 2022-09-12 17:14:37 -03:00
Gabriel Amorim 0913741500 Merge pull request #134 from dadosfera/fix/connections-migration
Fix: connections migration
2022-09-12 16:42:08 -03:00
Gabriel Rosa 3e9d0063ae FIX: using DadosferaLogger 2022-09-12 16:35:58 -03:00
Gabriel Rosa 2bc0fe1e9e FIX: get logger from DadosferaLogger 2022-09-12 16:35:08 -03:00
Gabriel Rosa f0fb6e6b4a FIX: DEBUG-ConnectionsMigrations 2022-09-12 16:31:10 -03:00
Gabriel Amorim 74c27d9637 Merge pull request #133 from dadosfera/fix/connections-migration
FIX: get all connectors fixed
2022-09-12 16:11:08 -03:00
Gabriel Rosa 216c83d7fa FIX: get all connectors fixed 2022-09-12 16:08:11 -03:00
Gabriel Amorim 08e66eeb1e Merge pull request #132 from dadosfera/fix/connections-migration
Fix: connections migration
2022-09-12 15:28:45 -03:00
Gabriel Rosa cf3fd90787 FIX: network_config migration 2022-09-12 15:27:54 -03:00
Gabriel Rosa bb1decb08f FIX: typo on dadosfera:connections-migration fixed 2022-09-12 13:01:54 -03:00
Gabriel Rosa 642cb0e70b FIX: connections migration 2022-09-12 13:01:11 -03:00
Gabriel Amorim 0acf759e84 FIX: treating undefined responses from OpenSearch
Merge pull request #131 from dadosfera/fix/connections-undefineds
2022-09-12 07:41:56 -03:00
Gabriel Rosa 7bbb87a54a FIX: removed fake customer_name 2022-09-11 13:08:00 -03:00
Gabriel Rosa 69af7ba626 FIX: new route for synchronizing connections with platform 2022-09-11 13:07:21 -03:00
Gabriel Amorim 0b14221d5a FEAT: Connections manager
FEAT: Connections manager
2022-09-09 17:46:50 -03:00
Gabriel Rosa 16bddf3d40 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-09 17:42:29 -03:00
Gabriel Rosa fb6ea7ba14 FIX: tests skipped 2022-09-09 17:42:01 -03:00
Gabriel Amorim 83d649a210 Merge branch 'main' into ft/connection-manager 2022-09-09 17:23:59 -03:00
Gabriel Rosa 319fc80bfe FIX: Checking if old pipelines exists 2022-09-09 11:56:39 -03:00
Gabriel Rosa 35172681ce FIX: checking if status is undefined 2022-09-09 10:17:22 -03:00
Gabriel Rosa 35db24dc59 FIX: find all pipelines 2022-09-09 09:48:19 -03:00
Gabriel Rosa ad9c1fc40c REFACTOR: changed old pipeline service to use lastValueFrom 2022-09-08 19:14:40 -03:00
Gabriel Rosa 4660751023 FIX: getting query params on find all pipelines 2022-09-08 17:59:15 -03:00
Gabriel Rosa 25cf13923b FIX: treating undefined language 2022-09-08 17:50:45 -03:00
Gabriel Rosa d724c1644b FIX: Findone pipeline 2022-09-08 17:12:24 -03:00
Gabriel Rosa c64529a37c FIX: adjust old pipeline input payload 2022-09-08 16:13:14 -03:00
Gabriel Rosa 8b06f0b12c Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-08 15:12:00 -03:00
Gabriel Rosa 311a4e4b93 FIX: salesforce oauth tye 2022-09-08 15:11:54 -03:00
rodrigo.zamboni 6f8985baa7 Merge branch 'ft/connection-manager' of https://github.com/dadosfera/maestro into ft/connection-manager 2022-09-08 15:06:52 -03:00
rodrigo.zamboni 11cb5a61da FIX: removing failing objectSnailtoCamel function 2022-09-08 15:06:12 -03:00
Gabriel Rosa 35c0520750 FIX: oauth conditionally setting state 2022-09-08 13:44:44 -03:00
Gabriel Rosa 8c6ec8b1b9 FIX: oauth creating connection 2022-09-08 13:34:51 -03:00
Gabriel Rosa 77db6f3771 New protospack 2022-09-07 17:31:21 -03:00
rodrigo.zamboni 952a76e905 FIX: fixing empty string netwoer_config_id field on test credentials 2022-09-07 11:22:03 -03:00
rodrigo.zamboni 2ec62dae45 FIX: Fix createInput columns field 2022-09-07 10:54:26 -03:00
rodrigo.zamboni 0d431a816b FIX: fix connection service client 2022-09-06 17:13:41 -03:00
rodrigo.zamboni 932e7413bd FIX: Solve merge conflicts 2022-09-06 17:02:55 -03:00
rodrigo.zamboni 5cb83ee679 FIX: Update protospack lib 2022-09-06 17:00:28 -03:00
Gabriel Rosa dcd558bd58 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-06 15:38:39 -03:00
Gabriel Rosa 01e74473c0 FIX: pipeline accepting properties on payload 2022-09-06 15:38:25 -03:00
Victor Radael d0c15fe290 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-06 12:29:39 -03:00
Victor Radael b9d07e3677 FIX: Connection Details 2022-09-06 12:22:30 -03:00
Gabriel Rosa 2c8ed40bc6 Inputs models refactor 2022-09-06 11:45:12 -03:00
Victor Radael 46d9fd8446 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-06 10:10:05 -03:00
Victor Radael 1ca7e647d3 new local env 2022-09-06 10:07:56 -03:00
Gabriel Rosa 469f165cba DOCS: all deprecated fields on create input request 2022-09-06 09:17:24 -03:00
Gabriel Rosa 203a161879 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-06 08:39:03 -03:00
Gabriel Rosa 31e9a07489 FIX: create input generic adjusted to new pipeline format 2022-09-06 08:38:46 -03:00
rodrigo.zamboni e24925036d Merge branch 'ft/connection-manager' of https://github.com/dadosfera/maestro into ft/connection-manager 2022-09-05 17:39:01 -03:00
rodrigo.zamboni 53f8f6986f FIX: fix default_value property parse 2022-09-05 17:38:22 -03:00
Gabriel Rosa 5e0a422b35 FIX: checking permission seqid on @AuthenticateCondition 2022-09-05 17:12:44 -03:00
rodrigo.zamboni 8294b5591b FIX: fix connectors permissions 2022-09-05 16:53:15 -03:00
rodrigo.zamboni 01d41a4586 FIX: fix merge conflicts 2022-09-05 16:22:34 -03:00
rodrigo.zamboni b8afdcf87d FIX: fixed permissions 2022-09-05 16:21:01 -03:00
Victor Radael fa216053c2 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-05 15:54:40 -03:00
Victor Radael e9a4a77eae Local Connections 2022-09-05 15:53:45 -03:00
Gabriel Rosa fe041262eb FIX: pack metadata before sending to createConnection on connection-test 2022-09-05 15:35:09 -03:00
Victor Radael 7ef0ff1502 FIX: Transformations 2022-09-05 13:06:31 -03:00
Victor Radael d8c55def7e FIX: Transformations 2022-09-05 13:05:53 -03:00
rodrigo.zamboni dfcc3dcb53 FIX: fixed some merge conflicts 2022-09-05 11:30:16 -03:00
rodrigo.zamboni 080c8fc52a FIX: Ping route adapted to use connection_id 2022-09-05 11:01:18 -03:00
Victor Radael ecf0b1de51 FIX: Ad SSL 2022-09-05 09:37:04 -03:00
Victor Radael 2248dbe5a3 FIX: Get All Pipelines 2022-09-04 19:36:42 -03:00
Victor Radael f5ec6661ff FIX: Old Pipeline KeepCase 2022-09-02 20:17:23 -03:00
Victor Radael 0deee0ef9c FIX: Old Pipeline KeepCase 2022-09-02 20:15:15 -03:00
Victor Radael 85f79f14cd FIX: Pipeline Imports 2022-09-02 18:14:41 -03:00
Victor Radael 01bdf134ca FEAT: GET One Pipelinev2 2022-09-02 14:15:36 -03:00
Victor Radael 10a89632b5 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-09-02 12:20:50 -03:00
Victor Radael 24d8f832fa Local Connections 2022-09-02 12:19:44 -03:00
Gabriel Rosa 8efe3d26f8 Add connection update permission (Internal only for now) 2022-09-02 09:11:52 -03:00
Victor Radael 6d21f29005 FEAT: Pipeline Version 2 2022-09-01 20:47:46 -03:00
Victor Radael e526180902 FEAT: Pipeline Version 2 2022-09-01 20:46:02 -03:00
Gabriel Rosa be37eb0321 New permissions 2022-09-01 17:49:38 -03:00
rodrigo.zamboni 8c85172554 Merge branch 'ft/connection-manager' of https://github.com/dadosfera/maestro into ft/connection-manager 2022-09-01 12:45:00 -03:00
rodrigo.zamboni 61296555fe FIX: not deleting failed connection when it comes from request body 2022-09-01 12:41:46 -03:00
Gabriel Rosa 5b6673a367 FIX: port can be either string or number on connection test ping 2022-09-01 09:15:06 -03:00
rodrigo.zamboni 2968647e73 FIX: add connection add parameter to function 2022-08-30 16:44:27 -03:00
rodrigo.zamboni a1a55d645a FIX: update protospack v2 version 2022-08-30 16:23:50 -03:00
rodrigo.zamboni b7748970cd FIX: Fix connectiontest ping dto 2022-08-30 15:37:11 -03:00
Victor Radael 8fb487435a FEAT: Connection Details new version 2022-08-26 19:25:01 -03:00
Victor Radael e94ace09d0 FEAT: Connection Details new version 2022-08-26 19:24:03 -03:00
Gabriel Rosa c713b65979 Better error handling on oauth 2022-08-23 16:14:57 -03:00
Gabriel Rosa 195fdae0e6 Saving oauth connections 2022-08-23 15:42:33 -03:00
Victor Radael 0f079a7b22 DOCS: Swagger 2022-08-23 15:03:13 -03:00
Victor Radael 93b44e8249 DOCS: Swagger 2022-08-23 15:01:28 -03:00
Victor Radael e18dc21670 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-08-23 14:52:47 -03:00
Victor Radael ae443b5b03 DOCS: Swagger 2022-08-23 14:52:36 -03:00
Gabriel Rosa 3bfcc7a412 checking if env is local to disable ssl 2022-08-23 09:01:17 -03:00
Gabriel Rosa 09e3b3fd33 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-08-22 11:35:22 -03:00
Gabriel Rosa 6b7500136f swagger back to normal 2022-08-22 11:35:08 -03:00
Victor Radael 7fa903fa1c FIX: Swagger 2022-08-22 11:27:46 -03:00
Victor Radael 19c35f5cc4 FIX: Swagger 2022-08-22 11:25:58 -03:00
Victor Radael 7b8b1bd8e0 FIX: Swagger 2022-08-22 11:22:41 -03:00
Gabriel Rosa 5cc2d704ea test swagger deploy 2022-08-22 11:13:22 -03:00
Gabriel Rosa d09bf96e6f FIX: added reference columns on get table metadata connection test 2022-08-22 10:23:06 -03:00
Victor Radael 0759f66144 FIX: Docs 2022-08-19 11:08:24 -03:00
Victor Radael 8bd5bf1526 FIX: Docs 2022-08-19 11:07:29 -03:00
Gabriel Rosa 9772e18332 Removed type and plugin from CreateNetworkConfigReq 2022-08-19 10:53:37 -03:00
Victor Radael 6f0f93d80d FIX: Docs Flow 2022-08-18 15:30:39 -03:00
Victor Radael 4b6d30859c FIX: Docs Flow 2022-08-18 15:24:30 -03:00
Victor Radael 5b4b1ae55e FIX: Docs Flow 2022-08-18 15:23:46 -03:00
Victor Radael 00dacfdbb9 FIX: Network_config 2022-08-18 14:22:14 -03:00
Victor Radael 7671567c8c FIX: Network_config 2022-08-18 14:21:46 -03:00
Victor Radael 64a8110649 FIX: Docs Flow 2022-08-18 12:03:31 -03:00
Victor Radael 4cbeea8281 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-08-18 11:38:00 -03:00
Victor Radael adf9c11358 FIX: Docs Flow 2022-08-18 11:37:49 -03:00
Gabriel Rosa 033752789a FIX: connection test ping and connection test credentials 2022-08-17 19:25:03 -03:00
Victor Radael 71617612b9 DOCS: Add Docs to Docsfera 2022-08-17 18:43:14 -03:00
Victor Radael 6219d61fe1 DOCS: Add Docs to Docsfera 2022-08-17 18:32:47 -03:00
Victor Radael 2fd51804fc DOCS: Add Docs to Docsfera 2022-08-17 18:12:32 -03:00
Victor Radael 874a3aa3a0 FIX: Connection and Network Flows (Create and Delete) 2022-08-17 16:03:37 -03:00
Victor Radael a9f68b87ca FIX: Connection and Network Flows (Create and Delete) 2022-08-17 15:58:00 -03:00
Gabriel Rosa 6807b3e1a9 FEAT: inputs and transformation using protospack-v2 2022-08-16 10:18:54 -03:00
Victor Radael 5f17a7106f Update Network Config Manager 2022-08-15 14:49:54 -03:00
Victor Radael 0a0ecb20bb FIX: Swagger 2022-08-12 16:35:36 -03:00
Victor Radael 35abb24ea7 FIX: Swagger 2022-08-12 16:21:26 -03:00
Victor Radael 372fc74bcb FEAT: Managers 2022-08-12 14:47:44 -03:00
Victor Radael 626f785994 O auth module 2022-08-12 13:57:44 -03:00
Victor Radael f88f559b70 Remove Separated Clients 2022-08-12 13:36:20 -03:00
Victor Radael 3981751ac0 Transformations Refactor 2022-08-12 12:49:40 -03:00
Victor Radael 61752a547f Pipelines Refactor 2022-08-12 12:38:34 -03:00
Victor Radael 3356f306b3 Client Refactor 2022-08-12 12:24:22 -03:00
Victor Radael 6dd894e11b Client Refactor 2022-08-12 12:22:41 -03:00
Victor Radael 7a1b5c7dbf Duc Client Refactor 2022-08-12 12:13:04 -03:00
Victor Radael afc187d01a Merge Input Refactor 2022-08-12 10:44:09 -03:00
Victor Radael d0cd62cd3c Merge Input Refactor 2022-08-12 10:43:20 -03:00
Victor Radael acd4dafc57 Update feature 2022-08-12 10:38:20 -03:00
Gabriel Rosa d8ce82793d REFACTOR: removed inputs separated client service 2022-08-11 17:39:50 -03:00
Gabriel Rosa 117704cd8c REFACTOR: inputs inside its own module 2022-08-11 14:28:10 -03:00
Gabriel Rosa 430e09c159 removed some unused tests and new test config 2022-08-11 10:08:06 -03:00
Gabriel Rosa fa11df5684 FIX: create network_config type and plugin default values 2022-08-10 12:33:35 -03:00
Gabriel Amorim e9b85537d7 Merge pull request #129 from dadosfera/feat/role-usage
FEAT: role usage
2022-08-09 09:38:09 -03:00
Gabriel Rosa 17060332fd new protospack release 2022-08-09 09:34:52 -03:00
Gabriel Rosa fde6d0a74c remove unused imports 2022-08-08 12:07:43 -03:00
Gabriel Rosa 0ca6ed8636 MERGE MAIN 2022-08-08 12:07:37 -03:00
Victor Radael 7c5cf3f541 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-08-08 11:01:50 -03:00
Victor Radael 54f4331bcc Update Managers 2022-08-08 10:55:22 -03:00
Gabriel Rosa 2f006c607c FIX: add usage on interfaces 2022-08-05 16:32:10 -03:00
Gabriel Rosa 9fbbfc2064 FIX: new protospack and better roles error handling 2022-08-05 16:20:20 -03:00
arthur simas ba12d45bf7 Merge pull request #128 from dadosfera/feat/snowflake-sso
FEAT: snowflake single sign-on
2022-08-04 17:40:35 -03:00
Arthur Simas ec418b00e7 FIX: relay state in response 2022-08-04 11:29:05 -03:00
Arthur Simas 89a02a4e8a FIX: added snowflake sso relaystate field 2022-08-04 11:11:42 -03:00
Gabriel Rosa cbee264182 FIX: Create VPN using multipart form data to fetch file 2022-08-03 16:30:32 -03:00
Gabriel Rosa 2514245a44 Merge main and conflicts solving 2022-08-03 13:36:31 -03:00
Gabriel Rosa 0761c42041 removed unused import 2022-08-03 11:34:02 -03:00
Gabriel Rosa 688bf49f3b FEAT: Connection test creating connection
- If any error occurs, connection is deleted
 - If operation_result is false, connection is deleted
2022-08-03 11:31:57 -03:00
Gabriel Rosa a1cdad42ab FIX: fixed tests modules 2022-08-03 10:37:17 -03:00
Gabriel Rosa 7546e6100d MERGE MAIN 2022-08-03 10:36:57 -03:00
Arthur Simas 7cf178db42 FEAT: fake commit 2022-08-02 16:50:35 -03:00
Arthur Simas 2673e75f81 CHORE: protospack-v2 bumped 2022-08-02 16:13:12 -03:00
Arthur Simas 6382939e1e FEAT: snowflake sso 2022-08-02 15:27:18 -03:00
arthur simas 3bea2e4cda Merge pull request #124 from dadosfera/feat/permissions
FEAT: permissions updated
2022-08-02 14:57:40 -03:00
Arthur Simas d9a7555d01 Merge branch 'main' into feat/permissions 2022-08-02 14:49:25 -03:00
arthur simas 5122dbde85 Merge pull request #126 from dadosfera/feat/scoped-packages
FEAT: scoped packages
2022-08-02 14:43:07 -03:00
Victor Radael 4e6883a404 Update Protospack Name and Network Config 2022-08-02 11:47:13 -03:00
Arthur Simas dcb3094890 FIX: protospack-v2 changes 2022-08-01 17:05:00 -03:00
Arthur Simas c8e4e90100 FIX: aws codeartifact region 2022-08-01 15:52:30 -03:00
Arthur Simas 09cff9fa15 CI: cleanup ci tests 2022-08-01 15:52:24 -03:00
Arthur Simas 79b624cea6 FEAT: using scoped package @dadosfera/dadosfera-logs 2022-08-01 15:52:18 -03:00
Arthur Simas 5356334258 FEAT: using scoped package @dadosfera/protospack-v2 2022-08-01 15:52:06 -03:00
Gabriel Amorim 5f6e4e88a5 Merge pull request #125 from dadosfera/fix/users-roles-management
FIX: sending access token to role function to evaluate editing permissions
2022-08-01 15:30:28 -03:00
Gabriel Rosa 32ab834834 FIX: fix user.decorator tests failing because of missing access_token on fakeUserPayload 2022-08-01 13:46:27 -03:00
Gabriel Rosa ebbfe0abff FIX: sending access token to role function to evaluate editing permissions 2022-08-01 09:17:08 -03:00
Arthur SimasandBeatriz Antunes 4fb24d5b5c FEAT: permissions updated
Co-Authored-By: Beatriz Antunes <beatrizaantunes@gmail.com>
2022-07-29 13:39:38 -03:00
arthur simas d76a2b4ab8 Merge pull request #123 from dadosfera/feat/user-management
FEAT: user management
2022-07-28 19:24:50 -03:00
Arthur Simas 047737eadd FIX: removing log from subscribe.complete method 2022-07-28 19:21:16 -03:00
Victor Radael 00687dc84e Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-07-28 17:31:38 -03:00
Victor Radael 61a1939c02 Save Connection Changes 2022-07-28 17:30:10 -03:00
Gabriel Rosa 43cec9b6fa FEAT: network config module using in-factory 2022-07-28 15:41:21 -03:00
Gabriel Rosa 97214280bc FEAT: network config module 2022-07-28 10:43:21 -03:00
Arthur Simas e20ae71672 FIX: resend invite endpoint renamed 2022-07-27 19:35:25 -03:00
Arthur Simas 97b6047cf1 FIX: avoiding to change user object received in adjustUsersPayload, returning modified object instead 2022-07-27 19:20:35 -03:00
Arthur Simas 9c6d2b499a FEAT: resend invite 2022-07-27 18:15:07 -03:00
Victor Radael 0de4a72388 Add OpenSearch 2022-07-27 15:56:04 -03:00
Victor Radael f65f1d01ce Add OpenSearch 2022-07-27 15:54:29 -03:00
Gabriel Rosa 6161865f1a FIX: Add authentication on connection-test routes 2022-07-27 14:25:25 -03:00
Gabriel Rosa d28684a9d0 Connected Connection Test routes to microservice 2022-07-27 14:19:25 -03:00
Arthur Simas f43c270c33 CHORE: protospack-v2 pre-release 2022-07-27 12:51:07 -03:00
Arthur Simas b0ccb39f2c FIX: adding error message on each failed user (BatchCreateUserRes) 2022-07-27 12:46:20 -03:00
Gabriel Rosa 54c2a41466 Merge branch 'ft/connection-manager' of github.com:dadosfera/maestro into ft/connection-manager 2022-07-27 08:42:33 -03:00
Gabriel Rosa 037f5cfec0 FEAT: new conection test module 2022-07-27 08:41:32 -03:00
Victor Radael 22631478b9 CRUD Connection 2022-07-25 17:08:01 -03:00
Gabriel Rosa 9f900882cb FIX: sending customerId on updateUser 2022-07-25 10:09:18 -03:00
Gabriel Rosa a24abed9ed updated protospack version 2022-07-22 08:53:27 -03:00
Gabriel Rosa b04a38bcc0 FIX: get departments and jobTitles 2022-07-22 08:45:58 -03:00
Victor Radael 349ae780a7 FEAT: Connection Manager 2022-07-21 15:19:05 -03:00
Victor Radael 60fb2ee7cb FEAT: Connection Manager 2022-07-21 09:31:37 -03:00
Gabriel Rosa 67706c5fc3 FIX: returning hierarchies the right way 2022-07-20 16:58:53 -03:00
Gabriel Rosa 0fa4dcb933 new protospack version 2022-07-20 15:37:01 -03:00
Gabriel Rosa 04284e1feb FIX: changing hierarchy strategy 2022-07-20 15:18:14 -03:00
Gabriel Rosa 3e7581e20c FIX: changing hierarchy strategy 2022-07-20 15:17:58 -03:00
Gabriel Rosa c61c37b268 FIX: new user columns 2022-07-20 09:50:58 -03:00
Gabriel Rosa 5ce8099c25 tests fixes 2022-07-19 16:22:24 -03:00
Gabriel Rosa 9cdb0a5d38 FIX: Update user accepting roleIds 2022-07-19 10:17:57 -03:00
Gabriel Rosa 57f47d3b2e FIX: get all hierarchies 2022-07-18 18:20:15 -03:00
Gabriel Rosa 4ab955a7a1 removed unused import 2022-07-18 18:00:44 -03:00
Gabriel Rosa 1ca3694b75 merge main 2022-07-18 18:00:18 -03:00
Gabriel Rosa a6a35c507a new Protospack 2022-07-18 17:56:50 -03:00
Gabriel Rosa cfcb8c1c42 FEAT: new user endpoints 2022-07-18 17:50:12 -03:00
Gabriel Rosa a55da8e20f FEAT: batch create user 2022-07-18 15:37:56 -03:00
Gabriel Rosa 82310c6314 FEAT: Users roles id = seqid 2022-07-18 14:04:35 -03:00
Gabriel Rosa 52f9d06ad0 FIX: get permissions route 2022-07-18 11:24:52 -03:00
Victor Radael a83f91a720 Merge pull request #122 from dadosfera/ft/connectors-manager
FEAT: Ft/connectors manager
2022-07-15 18:46:01 -03:00
Victor Radael ccef7628f9 FIX: Remove AuthController 2022-07-15 18:00:09 -03:00
Victor Radael a5e424c854 Update app.module.ts 2022-07-15 17:53:38 -03:00
Gabriel Rosa 339432df16 FIX: server urls on swagger 2022-07-15 15:24:42 -03:00
Victor Radael 7dfa10d5ee Merge branch 'ft/connectors-manager' of github.com:dadosfera/maestro into ft/connectors-manager 2022-07-15 15:10:40 -03:00
Victor Radael c87ceec77b FIX: Upload Imaga Files and add permissions 2022-07-15 15:10:11 -03:00
Gabriel Rosa 071c524a2e FIX: changed get roles to search roles 2022-07-15 14:39:30 -03:00
Gabriel Rosa 1de4eedd38 FEAT: new permission for user management 2022-07-15 12:27:49 -03:00
Gabriel Rosa 29276d6070 Merge branch 'main' into feat/user-management 2022-07-15 11:42:13 -03:00
Gabriel Rosa 344a251de1 merge main 2022-07-15 11:42:01 -03:00
Arthur Simas 3b95032554 Merge branch 'main' into ft/connectors-manager 2022-07-15 11:24:46 -03:00
arthur simas a0089b5d5d Merge pull request #121 from dadosfera/fix/terms-of-use-ip
FIX: terms of use ip:port
2022-07-15 11:15:12 -03:00
Gabriel Rosa 1b620b6fb0 FEAT: ROle management 2022-07-15 11:08:57 -03:00
Arthur Simas 1eb76d4b1c FIX: terms of use ip:port 2022-07-15 11:08:46 -03:00
Victor Radael 314bc5a562 Merge remote-tracking branch 'origin' into ft/connectors-manager 2022-07-14 17:34:17 -03:00
Victor Radael d01b0357ed FIX: Remove logs and add Authenticated 2022-07-14 17:31:12 -03:00
arthur simas a206aad8db Merge pull request #120 from dadosfera/feature/terms-of-use
FEAT: terms of use
2022-07-14 14:50:01 -03:00
Gabriel Rosa de57bfed1e FEAT: new roles routes working 2022-07-14 12:53:00 -03:00
Gabriel Rosa 14cfc46492 FEAT: Roles WIP 2022-07-14 10:23:04 -03:00
Victor Radael 9d10e58b1b FIX: SSL 2022-07-13 14:14:48 -03:00
Victor Radael 6dec2c1e56 FIX: Header Dadosfera-Lang 2022-07-13 14:11:35 -03:00
Victor Radael 23a65d18a6 Search Adapt fields 2022-07-12 17:04:25 -03:00
Gabriel Rosa d5cd562860 FEAT: get permissions working 2022-07-12 15:43:21 -03:00
Gabriel Rosa f5064420a7 FIX: missing import 2022-07-12 14:48:01 -03:00
Gabriel Rosa bb5befb2f6 FEAT: new route for updating Roles and changed hierarchy to string 2022-07-12 14:27:54 -03:00
Gabriel Rosa 398360fadb FEAT: new routes do add or remove permisisons from role 2022-07-12 12:11:03 -03:00
Victor Radael 13072978f5 FEAT: Accept-Language 2022-07-12 09:21:45 -03:00
Gabriel Rosa b9612609d9 FIX: add description on create role documentation 2022-07-11 16:58:40 -03:00
Victor Radael e7b37629a1 Insomnia File 2022-07-11 14:08:25 -03:00
Victor Radael 5b6994b247 Merge remote-tracking branch 'origin/main' into ft/connectors-manager 2022-07-11 12:26:02 -03:00
Gabriel Rosa 039f837914 FIX: tests fixing 2022-07-11 11:07:08 -03:00
Gabriel Rosa 83dcc7942c FIX: better code organization 2022-07-11 10:42:01 -03:00
Gabriel Rosa 5c079bb4f5 FIX: permissions contract 2022-07-11 10:09:06 -03:00
Arthur Simas f383cf49d5 FIX: added get token method 2022-07-11 02:13:53 -03:00
Arthur Simas db78ee3d8b FEAT: terms of use 2022-07-11 02:13:33 -03:00
Gabriel Rosa 730fab8ed1 FEAT: internationalization of permissions 2022-07-08 19:03:32 -03:00
Victor Radael 04e1e1ff2b New features to Connector's Search 2022-07-08 18:33:58 -03:00
Victor Radael d309c20cbf New features to Connector's Search 2022-07-08 18:32:06 -03:00
Gabriel Rosa 4cff593695 FEAT: Roles and permissions modules 2022-07-08 17:28:23 -03:00
Victor Radael 03d42df3fd Att Modules 2022-07-08 13:06:26 -03:00
Gabriel Rosa ea04f59e04 FIX: Fixed create user req 2022-07-08 12:28:40 -03:00
Gabriel Rosa d1961a9710 FEAT: User Management WIP 2022-07-08 12:04:56 -03:00
Gabriel Rosa 358dd6a15a merge main 2022-07-08 10:08:22 -03:00
arthur simas cc3b34d87e Merge pull request #119 from dadosfera/fix/error-handling
FIX: added exception filter to catch unhandled errors
2022-07-07 20:28:44 -03:00
Arthur Simas 88bc7fe625 FIX: added exception filter to catch unhandled errors 2022-07-07 20:24:27 -03:00
Gabriel Amorim 137f129e13 Merge pull request #118 from dadosfera/conditional-apm-import
FIX: Conditional apm import
2022-07-07 20:22:55 -03:00
Gabriel Rosa f49c5ac1c1 FIX: conditionally importing apm 2022-07-07 20:04:42 -03:00
Gabriel Rosa d288a4f357 FIX: conditionally importing apm 2022-07-07 18:18:53 -03:00
Gabriel Rosa 9f009309cb FIX: conditionally importing apm 2022-07-07 17:51:28 -03:00
Victor Radael 395db05b05 Merge main 2022-07-07 17:17:57 -03:00
Victor Radael 2d69e6db70 Merge main 2022-07-07 17:13:30 -03:00
Victor Radael 9514134db1 Merge main 2022-07-07 16:58:33 -03:00
Victor Radael 0c69cebca1 Merge main 2022-07-07 16:55:52 -03:00
Victor Radael 9791b8bdb0 Merge main 2022-07-07 16:55:04 -03:00
Victor Radael 6803ab3420 Merge main 2022-07-07 16:36:28 -03:00
Victor Radael 6aa7be98d2 Merge main 2022-07-07 16:32:17 -03:00
Gabriel Rosa f5665557c9 FIX: better parameter naming 2022-07-07 16:17:45 -03:00
arthur simas 366987399e Merge pull request #117 from dadosfera/refactor/protospack-v2
FEAT: protospack-v2 updated
2022-07-07 16:05:00 -03:00
Gabriel Rosa 4356adea65 FEAT: User module init 2022-07-07 15:57:40 -03:00
Arthur Simas 4c49dbc0ea FEAT: protospack-v2 updated 2022-07-07 15:00:59 -03:00
arthur simas 59ffb24985 Merge pull request #116 from dadosfera/refactor/auth-module
FEAT: auth module refactored
2022-07-06 17:47:54 -03:00
Arthur Simas 1cd191e970 FEAT: auth module refactored 2022-07-06 17:20:00 -03:00
arthur simas 7e92ca2c36 Merge pull request #113 from dadosfera/refactor/auth
auth unit tests (+ readme updated)
2022-07-06 14:10:13 -03:00
Arthur Simas 12af510d36 DOCS: readme improved 2022-07-06 11:55:19 -03:00
Arthur Simas 61871958aa DOCS: readme improved 2022-07-06 11:55:19 -03:00
Arthur Simas 64d84f9148 FEAT: refactor authentication and user decorators + unit tests 2022-07-06 11:55:19 -03:00
Arthur Simas 2c099ba491 CI: removing npm token on build 2022-07-06 11:55:19 -03:00
Arthur Simas 65b2578f15 FIX: removing useless tests 2022-07-06 11:55:19 -03:00
Gabriel Amorim 68e5534edb Merge pull request #115 from dadosfera/fix/recreate-input
FIX: recreating input getting id the right way
2022-07-06 11:54:42 -03:00
Gabriel Rosa 51c7bdfebf FIX: recreating input getting id the right way 2022-07-06 11:53:22 -03:00
Rodrigo Zamboni 9f6d2c7c9c Merge pull request #114 from dadosfera/feat/logs-lib
HOTFIX - FIX: delete info field before using it
2022-07-06 11:11:20 -03:00
rodrigo.zamboni 02955b99fe FIX: delete info field before using it 2022-07-06 11:06:52 -03:00
Rodrigo Zamboni 93505d3268 Merge pull request #112 from dadosfera/feat/logs-lib
Add Logs Lib and APM to prd
2022-07-06 09:55:48 -03:00
rodrigo.zamboni e5bf5b23e7 FIX: Kibana Transactions working 2022-07-05 17:00:12 -03:00
rodrigo.zamboni 0d20b4dcab UPDATE: Prepared Maestro for APM 2022-07-01 17:53:15 -03:00
rodrigo.zamboni bb5cf0494d FIX: Remove health check logs message 2022-07-01 17:02:52 -03:00
rodrigo.zamboni c058e54c90 FIX: Remove health check logs message 2022-07-01 16:51:21 -03:00
rodrigo.zamboni e82166f2c7 FIX: fix Capital letter service name 2022-07-01 16:30:41 -03:00
Victor Radael 8d23eaeea2 Code artifact and Get Connectors Tags 2022-06-30 16:55:46 -03:00
rodrigo.zamboni f8b495036d Merge branch 'passport/facebook' into feat/logs-lib 2022-06-30 14:13:23 -03:00
Gabriel Rosa b03b8f3b08 FEAT: passport facebook 2022-06-30 12:35:47 -03:00
Gabriel Amorim aaf99b8c2e FEAT: passport salesforce and improvements connector manager
Merge pull request #111 from dadosfera/feature/passport-salesforce
2022-06-30 10:28:58 -03:00
rodrigo.zamboni 426ff2141f FIX: Changed registry after adding protospack to codeartifact 2022-06-29 17:59:53 -03:00
Gabriel Rosa 8060cd769f merge connector-manager 2022-06-29 17:27:21 -03:00
rodrigo.zamboni ac021ea7e1 FIX: dockerfile 2022-06-29 17:25:53 -03:00
Gabriel Rosa 89eed11561 FIX: get salesforce access token and refresh token 2022-06-29 17:25:29 -03:00
rodrigo.zamboni abe78bff9b FIX: dockerfile 2022-06-29 16:46:28 -03:00
rodrigo.zamboni 3a26c5bebf FIX: dockerfile 2022-06-29 15:45:04 -03:00
rodrigo.zamboni 2af0b3af52 FIX: dockerfile 2022-06-29 15:39:36 -03:00
rodrigo.zamboni c28bc74b3f FIX: fix commands 2022-06-29 15:23:08 -03:00
rodrigo.zamboni 7eae88b5cd FIX: Added aws cli installation on dockerfile 2022-06-29 15:15:13 -03:00
rodrigo.zamboni 1e66065a21 FIC: add codeartifact login on dockerfile 2022-06-29 15:06:01 -03:00
rodrigo.zamboni 1e407f081e added package.lock 2022-06-29 14:49:49 -03:00
rodrigo.zamboni 31c507d667 FIX: Merged with radaels branch 2022-06-29 14:27:17 -03:00
rodrigo.zamboni a39622bfa1 UPDATE: Added log-lib on more routes and services 2022-06-29 14:12:13 -03:00
Victor Radael 0895922b1c Merge main 2022-06-29 14:12:01 -03:00
Victor Radael 1cc094d40b Merge main 2022-06-29 14:09:33 -03:00
Victor Radael d13aa2b72a Swagger 2022-06-29 13:59:46 -03:00
Victor Radael a98ec4bf32 Merge branch 'merge/auth+connectors-manager' of github.com:dadosfera/maestro into merge/auth+connectors-manager 2022-06-29 13:59:15 -03:00
Victor Radael d5b1cb51b4 Search Adapt fields 2022-06-29 13:58:32 -03:00
Arthur Simas 5d3510639e FEAT: salesforce passport 2022-06-29 12:39:54 -03:00
rodrigo.zamboni bc0d456c2a merge with main 2022-06-29 12:28:00 -03:00
rodrigo.zamboni b658eacce6 pull from main 2022-06-29 10:56:43 -03:00
rodrigo.zamboni 9c124be191 UPGRADE: Added LOgs lib 2022-06-29 10:50:03 -03:00
Gabriel Amorim dd4a1ecd46 FEAT: passport+connector manager
Merge pull request #110 from dadosfera/feat/passport+connector-manager
2022-06-28 19:09:40 -03:00
Gabriel Rosa 6eaaa746d0 FIX: merge conflict fixed 2022-06-28 18:40:52 -03:00
Gabriel Rosa df79fbdab0 Merge branch 'merge/auth+connectors-manager' of github.com:dadosfera/maestro into merge/auth+connectors-manager 2022-06-28 18:37:44 -03:00
Gabriel Rosa 0f34152b02 Merge branch 'feat/passport+connector-manager' of github.com:dadosfera/maestro into feat/passport+connector-manager 2022-06-28 18:36:49 -03:00
Gabriel Rosa 08d576432e FIX: new permissions 2022-06-28 18:36:41 -03:00
arthur simas ce0362217c Merge pull request #109 from dadosfera/feat/new-permissions
FEAT: new orchest permissions
2022-06-28 18:31:03 -03:00
Arthur Simas 3399f125e5 Merge branch 'feat/new-permissions' into feat/passport+connector-manager 2022-06-28 18:02:53 -03:00
Arthur Simas 9e0a58b4b7 FEAT: new orchest permissions 2022-06-28 18:02:01 -03:00
Gabriel Rosa 49a9c93d6f merge main 2022-06-28 15:24:21 -03:00
Gabriel Rosa ddb81d3082 REFACTOR: removed unused imports 2022-06-28 12:31:56 -03:00
Gabriel Rosa 9d9d903335 FIX: Better error handling on pipeline create 2022-06-28 12:22:00 -03:00
arthur simas 3ddbfcebeb FEAT: new orchest permission + added missing field user.createdAt on signin payload (#108) 2022-06-28 11:52:53 -03:00
Gabriel Rosa e1d0aa00f5 swagfer changed 2022-06-28 10:24:54 -03:00
Gabriel Rosa 4209b5b23e merge with connector manager 2022-06-28 09:26:45 -03:00
Arthur Simas d6607b1d9f FEAT: connector manager 2022-06-27 19:22:36 -03:00
Arthur Simas 0eef0713df FIX: logging errors only if auth is required 2022-06-27 18:32:08 -03:00
Arthur Simas 17d629b430 FIX: suppressing health check not authenticated log 2022-06-27 18:32:08 -03:00
Arthur Simas 5362a99b24 CHORE: added protospack@v2 correct version 2022-06-27 18:32:08 -03:00
Arthur Simas 1566efc490 FEAT: squash - auth guard
nestjs access controll mock

swagger implementation

middleware changes and lib

permissions file back to maestro

FEAT: sending permissions to duc on startup


FEAT: new permissions.enum format


FEAT: permissions injection (maestro -> duc)


FIX: changing method invocation order


FEAT: refactored permission enum + duc permission injector + grpcHandler


FEAT: auth guards


FEAT: seq ids updated
2022-06-27 18:32:08 -03:00
Gabriel Rosa 91aefe7d61 FEAT: passport mailchimp 2022-06-27 17:50:45 -03:00
Gabriel Rosa b584c80e24 FIX: google-oauth redirect_uri fixed 2022-06-27 16:13:58 -03:00
Gabriel Rosa 74a6eb8132 FIX: google redirect uri fixed 2022-06-27 15:51:24 -03:00
Gabriel Rosa 7012a6e2ac FIX: google strategy different callbacks for google 2022-06-27 14:13:18 -03:00
Gabriel Rosa 2f5d940b46 FIX: google strategy custom scopes 2022-06-27 14:02:51 -03:00
Gabriel Rosa 06a0ed3299 merge new auth with passport oAuth 2022-06-27 11:16:16 -03:00
Gabriel Rosa f08e9dc1a8 FEAT: mailchimp config 2022-06-27 10:03:49 -03:00
Arthur Simas a0c19b587c FIX: logging errors only if auth is required 2022-06-25 20:07:34 -03:00
Arthur Simas 3fe91ad4e8 FIX: logging errors only if auth is required 2022-06-25 20:07:16 -03:00
Gabriel Rosa 07f2d56b9b FEAT: Mailchimp strategy 2022-06-24 16:20:19 -03:00
Arthur Simas 1668d55679 MERGE: auth + connector manager 2022-06-24 14:46:37 -03:00
Gabriel Rosa 498ea61c06 google oauth working 2022-06-24 13:03:16 -03:00
Gabriel Rosa 07f123e528 New strategies 2022-06-24 11:08:38 -03:00
Gabriel Rosa e439823319 update libs 2022-06-24 10:28:05 -03:00
Arthur Simas 55fb1a8b34 FIX: suppressing health check not authenticated log 2022-06-23 23:38:24 -03:00
Arthur Simas 8ce7a692b6 CHORE: added protospack@v2 correct version 2022-06-23 22:03:04 -03:00
Arthur Simas c7997ed8aa FEAT: squash - auth guard
nestjs access controll mock

swagger implementation

middleware changes and lib

permissions file back to maestro

FEAT: sending permissions to duc on startup


FEAT: new permissions.enum format


FEAT: permissions injection (maestro -> duc)


FIX: changing method invocation order


FEAT: refactored permission enum + duc permission injector + grpcHandler


FEAT: auth guards


FEAT: seq ids updated
2022-06-23 22:02:49 -03:00
Gabriel Amorim 89b571d7f8 FIX: Use passport for oauth
Merge pull request #105 from dadosfera/feature/passport-oauth
2022-06-23 18:44:25 -03:00
Gabriel Rosa 7776d61f82 fix frontend redirect url 2022-06-23 17:02:33 -03:00
Victor Radael 0e2f935807 Add insomnia file att 2022-06-23 16:59:08 -03:00
Gabriel Rosa 9bf7cc660e tiny code organization improvements 2022-06-23 16:31:48 -03:00
Gabriel Rosa d6552678a8 get credentials from AWS secrets manager 2022-06-23 16:26:49 -03:00
Victor Radael 9e25a1ffa7 Add change body to query params 2022-06-23 16:09:45 -03:00
Victor Radael 44ceee0671 Add change body to query params 2022-06-23 16:08:17 -03:00
Gabriel Rosa a42ec164c3 Merge branch 'fix/oauth-generic' into feature/passport-oauth 2022-06-23 11:43:43 -03:00
Gabriel Rosa 6abc2595cb get oauthSecrets from secrets manager 2022-06-23 11:34:12 -03:00
Gabriel Rosa b850596d64 get oauth secrets from secrets manager 2022-06-22 17:51:30 -03:00
Gabriel Rosa da3b7f3096 hubspot oauth 2022-06-22 15:24:36 -03:00
rodrigo.zamboni 3c660738f4 installed logs lib 2022-06-22 10:23:54 -03:00
Gabriel Rosa ff2b8fae6f generic axios 2022-06-22 09:49:23 -03:00
Victor Radael 44337dc4ed Add Insomnia Folder 2022-06-21 18:05:56 -03:00
Victor Radael 80e7aa7b9c Fix and add upload file 2022-06-21 18:01:21 -03:00
Victor Radael 6f4bb9ce6d Fix and add upload file 2022-06-21 18:01:06 -03:00
Gabriel Amorim 4df23c917a FEAT: new connectors
Merge pull request #103 from dadosfera/feature/hubspot-connector
2022-06-21 11:44:08 -03:00
Victor Radael 92afbb5e55 Find All 2022-06-21 10:23:14 -03:00
Gabriel Rosa 6c3eef7181 update protospack and deploy file 2022-06-21 10:13:20 -03:00
Gabriel Rosa 072a9fee8e hubspot guard 2022-06-20 17:09:28 -03:00
Victor Radael 953c0ac41e Register Connector 2022-06-20 15:37:45 -03:00
Gabriel Rosa 8c91551cdc incomplete oauth 2022-06-20 15:07:23 -03:00
Gabriel Rosa 668c5fe6a5 WIP: Documentation of input API 2022-06-20 11:36:26 -03:00
Gabriel Rosa d057495b2e FIX: redirect_uri.redirect_uri fixed 2022-06-15 15:19:45 -03:00
Gabriel Rosa 2f1c71eefe update protospack and better adjustInputPayload 2022-06-15 11:14:22 -03:00
Gabriel Rosa 3bb014e714 get redirect_uri from get_tokens_url_params 2022-06-15 10:08:03 -03:00
Gabriel Rosa 2ff2aa049f update protospack 2022-06-15 08:52:30 -03:00
Gabriel Rosa f6c7420f59 finding inputs using generic strategy 2022-06-15 08:44:21 -03:00
Gabriel Rosa abbe88f45b removed unused statement 2022-06-14 14:02:06 -03:00
Gabriel Rosa 383741f48c get entities 2022-06-14 14:01:40 -03:00
Gabriel Rosa ff663e7166 update protospack 2022-06-14 11:35:09 -03:00
Gabriel Rosa 305daeeeab update protospack 2022-06-13 16:50:38 -03:00
Gabriel Rosa 852ddbf88b transforming to camelCase before sending to in-factory 2022-06-13 15:55:02 -03:00
Gabriel Rosa 4dfe6c3870 fallback for env vars 2022-06-13 13:12:01 -03:00
Gabriel Rosa 2710159a73 minor improvements 2022-06-13 11:17:41 -03:00
Gabriel Rosa 1ab5d49908 removing info that won't be saved on database 2022-06-13 10:49:27 -03:00
Gabriel Amorim 43372b5a0f FIX: Better error handling on input creation
Merge pull request #102 from dadosfera/fix/error-handling
2022-06-10 16:05:29 -03:00
Gabriel Rosa 0ac2dd2de7 Better error handling on input creation 2022-06-10 15:57:19 -03:00
Gabriel Rosa 0342386a76 oauth almost complete 2022-06-09 17:14:11 -03:00
Gabriel Rosa 28e55f8fd9 update node version 2022-06-08 11:12:51 -03:00
Gabriel Rosa 4e2e296442 Fixed cron on create S3 2022-06-08 10:31:59 -03:00
Arthur Simas d89573bc79 FIX: creating swap file 2022-06-06 17:35:26 -03:00
Gabriel Amorim 03ed4d88a0 FIX: validate cron
Merge pull request #101 from dadosfera/fix/validate-cron
2022-05-31 16:33:05 -03:00
Gabriel Rosa 329bbf2e4d Better cron validation 2022-05-31 16:10:21 -03:00
Gabriel Rosa ae069a0e00 removed unused imports 2022-05-31 12:04:32 -03:00
Gabriel Rosa d3bcbdc43c validating cron when creating or updating input 2022-05-31 11:34:28 -03:00
Arthur Simas 4e2e6787a4 Merge branch 'feat/change-password' into main 2022-05-30 18:27:02 -03:00
Arthur Simas 50da33b766 FEAT: added verifyPasswordResetCode endpoint 2022-05-30 17:17:50 -03:00
Rodrigo Zamboni bb72cfce45 Merge pull request #100 from dadosfera/tests
Tests
2022-05-30 10:57:23 -03:00
rodrigo.zamboni 03d17758ae FIX: commented broken test 2022-05-30 10:11:04 -03:00
rodrigo.zamboni b7e76a6d3f FIX: fix busboy npm critical error 2022-05-30 09:58:07 -03:00
Arthur Simas c920eb27b9 FEAT: reset password 2022-05-27 18:57:05 -03:00
Arthur Simas c25d2427c2 FEAT: change password 2022-05-27 18:55:43 -03:00
Arthur Simas 0f13d0c590 FIX(refresh token): removed username param 2022-05-27 18:55:35 -03:00
Arthur Simas 7781298e6e Merge branch 'hotfix/refresh-token-contract' into fix/create-partial-input 2022-05-27 15:25:00 -03:00
Arthur Simas 2c00d3249a FIX(refresh token): removed username param 2022-05-27 15:12:22 -03:00
Arthur Simas cd7c7d538f CI: deploy workflow updated 2022-05-26 15:38:36 -03:00
rodrigo.zamboni e496d42776 ódio 2022-05-26 10:13:42 -03:00
Gabriel Rosa a07247d538 update protospack version 2022-05-25 15:49:45 -03:00
Arthur Simas 153ba1a98b CI: printing env 2022-05-25 12:24:50 -03:00
Gabriel Rosa 46519323f8 Ci: imporved github flow 2022-05-25 11:56:26 -03:00
Arthur Simas a51d52d68e CI: fixed versioning 2022-05-25 11:53:44 -03:00
Gabriel Rosa 555ff2eda9 CI - github flow melhorado 2022-05-25 11:52:54 -03:00
Arthur Simas 5d1a54a720 CI: . 2022-05-25 11:45:37 -03:00
Arthur Simas 8399a0e4c1 CI: . 2022-05-25 11:40:38 -03:00
Arthur Simas cf54cb0bc2 CI: not considering semver during manual deploy 2022-05-25 11:37:14 -03:00
rodrigo.zamboni 4eba099390 Merge branch 'main' of https://github.com/dadosfera/maestro into tests 2022-05-25 11:14:45 -03:00
rodrigo.zamboni 08fa269ce0 initial tests 2022-05-25 11:14:40 -03:00
Gabriel Rosa e52af6844b FIX: create partial input 2022-05-25 11:09:24 -03:00
Arthur Simas 2344e4dba3 CI: using github-flow deployment workflow 2022-05-24 18:57:41 -03:00
rodrigo.zamboni d686753f25 DOCS: Added proper readme 2022-05-23 17:19:36 -03:00
Rodrigo Zamboni c1498f8f1b Merge pull request #98 from dadosfera/beta
Beta -> main
2022-05-20 17:27:29 -03:00
Gabriel Rosa 0b3cb581c4 merge alpha -> beta 2022-05-20 15:19:43 -03:00
Gabriel Rosa 97a6e05812 FIX: returning input when creating jdbc 2022-05-20 15:19:00 -03:00
Gabriel Rosa 356d438404 merge alpha 2022-05-20 14:07:02 -03:00
Gabriel Rosa cf870cff14 FIX: formating payload 2022-05-20 14:06:13 -03:00
Gabriel Rosa 26c6a289eb merge from beta 2022-05-20 11:51:48 -03:00
Gabriel Rosa 8d27e42fcd FIX: protospack version 2022-05-20 11:51:08 -03:00
Gabriel Rosa c44e557b11 merge beta 2022-05-20 10:08:20 -03:00
Gabriel Rosa 25d06ab4c5 FIX: send input to createS3 2022-05-20 10:07:38 -03:00
Gabriel Amorim 31bb795d23 Merge pull request #97 from dadosfera/alpha
Alpha -> Beta
2022-05-20 09:12:34 -03:00
Gabriel Rosa a2e25eb737 FIX: added ssl back 2022-05-20 08:51:25 -03:00
Gabriel Rosa 6be53bebaf FIX: adjusting pipeline payload fixed 2022-05-20 08:50:08 -03:00
Rodrigo Zamboni 741f009723 Merge pull request #96 from dadosfera/alpha
Alpha
2022-05-19 15:20:06 -03:00
Rodrigo Zamboni 45477b5750 Merge branch 'beta' into alpha 2022-05-19 15:09:15 -03:00
rodrigo.zamboni 36de53dfd3 FIX: lib version 2022-05-19 14:48:22 -03:00
rodrigo.zamboni cd8fe2fbf6 CI: Deploy 2022-05-19 14:46:17 -03:00
rodrigo.zamboni c4a2bee69c CI: DEploy 2022-05-19 14:18:08 -03:00
rodrigo.zamboni e48d11153d FIX: Added S3 or JDBC input selection for pipelines 2022-05-19 14:13:06 -03:00
Gabriel Amorim c7e4de7d65 Merge pull request #95 from dadosfera/feat/mfa
FEAT: MFA finished + better error handling
2022-05-19 13:59:46 -03:00
Arthur Simas b2e7870cc5 CHORE: protospack version bump 2022-05-19 12:56:32 -03:00
Arthur Simas 1e239026a3 FEAT: MFA finished + better error handling 2022-05-19 12:02:08 -03:00
Rodrigo Zamboni bc108d43e3 Merge pull request #94 from dadosfera/beta
Beta
2022-05-18 16:54:04 -03:00
Arthur Simas 1c5b929c1b REFACTOR: nimbus URL 2022-05-18 13:57:19 -03:00
rodrigo.zamboni 3e21c308f6 UPDATE: Added S3 Input Creation 2022-05-17 15:58:18 -03:00
Gabriel Rosa f4a5f91cbb FIX: remove backmerge 2022-05-16 17:03:12 -03:00
Gabriel Rosa c8aeb7f3db FIX: remove backmerge 2022-05-16 17:02:46 -03:00
Victor Radael b9453b11cf REFACTOR: Init Catalog Class 2022-05-16 14:02:18 -03:00
Victor Radael a69752332e FIX: Nimbus URL Strategy 2022-05-16 13:42:06 -03:00
Victor Radael 09c7cc79ee FIX: Nimbus URL Strategy 2022-05-16 12:04:13 -03:00
Arthur Simas 65f6f03e55 CHORE: pinning protospack version to 1.7.4 2022-05-13 11:15:05 -03:00
Arthur Simas a54300f600 CHORE: pinning protospack version to 1.7.2 2022-05-13 11:00:25 -03:00
Arthur Simas 5ef85f79d3 Merge branch 'alpha' into beta 2022-05-13 10:49:27 -03:00
rodrigo.zamboni a8a429977f UPDATE: Implementing MFA routes on maestro 2022-05-11 18:02:01 -03:00
Gabriel Amorim 692a9de0af FEAT: add refresh token - Merge pull request #90 from dadosfera/feat/refreshToken
FEATURE: add refresh token
2022-05-11 10:37:36 -03:00
rodrigo.zamboni 7dc669ca44 FIX: fix auth trim 2022-05-10 15:53:47 -03:00
rodrigo.zamboni e897da1ef0 FIX: Fix login middleware 2022-05-10 15:32:34 -03:00
Arthur Simas b366e786ba STYLE: styling conforming to ESLint 2022-05-10 10:23:55 -03:00
Arthur Simas 24d56c7a58 FEAT: add refresh token 2022-05-09 11:47:25 -03:00
rodrigo.zamboni d7f205901b UPDATE: Changing transformation factory microservice client url to inputs factory 2022-05-05 16:27:20 -03:00
rodrigo.zamboni 7212c5249c FIX: testing timeout on testconnection input client 2022-05-04 16:44:00 -03:00
rodrigo.zamboni 827c7b1bf1 FIX: Summary-rating returning default 0 values 2022-05-04 12:00:01 -03:00
rodrigo.zamboni 8d89b747b4 FIX: Fix delete routes method 2022-05-04 00:24:44 -03:00
rodrigo.zamboni 4f18328603 FIX: update protospack lib version 2022-05-03 17:59:51 -03:00
rodrigo.zamboni a68d312447 FIX: updated protospack lib 2022-05-03 17:07:57 -03:00
rodrigo.zamboni 7f677f0376 FIX: Fix mistaken routes method 2022-05-03 16:54:03 -03:00
rodrigo.zamboni bb2c0ab23e Merge branch 'main' of https://github.com/dadosfera/maestro into main 2022-05-03 15:13:10 -03:00
rodrigo.zamboni feec48114c FIX: fixed missing nimbus catalog routes 2022-05-03 15:12:18 -03:00
Rodrigo Zamboni 7bee8d83a9 Merge pull request #89 from dadosfera/beta
Beta
2022-05-03 09:14:43 -03:00
Rodrigo Zamboni 84b2dbd178 Merge pull request #88 from dadosfera/alpha
CI: Deploy on dev env
2022-05-02 17:53:46 -03:00
rodrigo.zamboni 36706ff3dd CI: Deploy on dev env 2022-05-02 17:33:03 -03:00
Victor Radael 0164e6978a FIX: Metabase URL 2022-04-29 20:53:17 -03:00
rodrigo.zamboni 7d371926f5 FIX: fix nimbus prd url 2022-04-29 17:20:23 -03:00
rodrigo.zamboni 75e9e97f04 FIX: fix nimbus prd url 2022-04-29 17:18:21 -03:00
rodrigo.zamboni b5f58b99fd FIX: Changing nimbus prd url 2022-04-29 16:33:42 -03:00
rodrigo.zamboni 503293439f FIX: updating protospack lib version 2022-04-29 16:14:14 -03:00
rodrigo.zamboni eed57ff039 FIX: fix pipeline status sort 2022-04-29 12:42:49 -03:00
rodrigo.zamboni ef2a24a0c3 FIX: Fix runPipeline fail case return 2022-04-28 17:39:05 -03:00
Rodrigo Zamboni 8a31e11445 Merge pull request #87 from dadosfera/beta
Beta
2022-04-28 17:23:54 -03:00
Rodrigo Zamboni 2013e97e20 Merge pull request #86 from dadosfera/alpha
Alpha
2022-04-28 17:12:53 -03:00
rodrigo.zamboni dd271205ba Merge branch 'alpha' of https://github.com/dadosfera/maestro into alpha 2022-04-28 17:00:44 -03:00
rodrigo.zamboni a1f9f14897 FIX: Fixing more catalog routes params 2022-04-28 17:00:08 -03:00
Gabriel Amorim b7efa2f58d Merge pull request #85 from dadosfera/beta
Beta -> Main
2022-04-28 16:04:10 -03:00
Gabriel Amorim 395e36581d Merge pull request #84 from dadosfera/alpha
Alpha -> Beta
2022-04-28 16:03:53 -03:00
rodrigo.zamboni 97a54f9267 Merge branch 'alpha' of https://github.com/dadosfera/maestro into alpha 2022-04-28 15:56:30 -03:00
rodrigo.zamboni 00b0e00bd0 FIX: fix iframe_url and pipeline status 2022-04-28 15:55:48 -03:00
Rodrigo Zamboni 9188536161 Merge pull request #83 from dadosfera/beta
Beta
2022-04-28 14:53:16 -03:00
Rodrigo Zamboni 1ae8151184 Merge pull request #82 from dadosfera/alpha
Alpha -> Beta
2022-04-28 14:40:14 -03:00
rodrigo.zamboni 3d77980dd2 CI: deploy 2022-04-28 14:24:28 -03:00
Rodrigo Zamboni 849b979e63 Merge pull request #81 from dadosfera/feat/CatalogFixes
Feat/catalog fixes
2022-04-28 14:19:31 -03:00
rodrigo.zamboni 28580782b1 FIX: catalog routes fix 2022-04-28 14:14:03 -03:00
Rodrigo Zamboni fc784e2d6d Merge pull request #79 from dadosfera/alpha
Alpha
2022-04-28 13:22:14 -03:00
rodrigo.zamboni c997e91912 FIX: fix table-metadata find one route 2022-04-28 12:59:35 -03:00
rodrigo.zamboni 22f493d48a FIX: fixing missing id on delete routes 2022-04-28 12:12:37 -03:00
rodrigo.zamboni 035213824b FIX: fixing missing id on delete routes 2022-04-28 12:00:19 -03:00
rodrigo.zamboni 1ffa9ade51 FIX: CI 2022-04-28 10:53:17 -03:00
Rodrigo Zamboni c91aa57344 Merge pull request #78 from dadosfera/beta
Beta
2022-04-27 17:36:54 -03:00
Rodrigo Zamboni 077a95cdbd Merge pull request #77 from dadosfera/alpha
Alpha
2022-04-27 17:16:09 -03:00
Rodrigo Zamboni f39108772b Merge pull request #76 from dadosfera/NewInput
FIX: fix pipeline activate route method
2022-04-27 16:57:56 -03:00
rodrigo.zamboni cdd8beee8f FIX: fix pipeline activate route method 2022-04-27 16:56:03 -03:00
rodrigo.zamboni 14331ed229 FIX: fix pipeline activate route method 2022-04-27 16:54:48 -03:00
Victor Radael 5f3db0cb43 Merge pull request #75 from dadosfera/beta
Beta -> Main
2022-04-27 11:42:18 -03:00
Rodrigo Zamboni 28f304c47c Merge pull request #74 from dadosfera/alpha
Alpha
2022-04-26 17:41:57 -03:00
Rodrigo Zamboni 0c1b62b2aa Merge pull request #73 from dadosfera/NewInput
FIX: Fix get-columns route method
2022-04-26 17:25:11 -03:00
rodrigo.zamboni 13f7e9d812 FIX: Fix get-columns route method 2022-04-26 17:19:18 -03:00
Gabriel Amorim 841dcfef06 Merge pull request #72 from dadosfera/alpha
Alpha
2022-04-26 16:30:31 -03:00
Gabriel Amorim f41ddc1483 Merge pull request #71 from dadosfera/NewInput
UPDATE: Implemented new Input format
2022-04-26 16:19:19 -03:00
rodrigo.zamboni 0a2c7c0a38 UPDATE: Implemented new Input format 2022-04-26 16:15:54 -03:00
149 changed files with 16775 additions and 7069 deletions
+3
View File
@@ -0,0 +1,3 @@
container_commands:
01setup_swap:
command: "bash .ebextensions/setup_swap.sh"
+7
View File
@@ -0,0 +1,7 @@
Resources:
AWSEBAutoScalingLaunchConfiguration:
Properties:
MetadataOptions:
HttpEndpoint: enabled
HttpPutResponseHopLimit: 2
HttpTokens: required
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
SWAPFILE=/var/swapfile
SWAP_MEGABYTES=1024
if [ -f $SWAPFILE ]; then
echo "Swapfile $SWAPFILE found, assuming already setup"
exit;
fi
/bin/dd if=/dev/zero of=$SWAPFILE bs=1M count=$SWAP_MEGABYTES
/bin/chmod 600 $SWAPFILE
/sbin/mkswap $SWAPFILE
/sbin/swapon $SWAPFILE
+1
View File
@@ -6,6 +6,7 @@ module.exports = {
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
+206
View File
@@ -0,0 +1,206 @@
name: Deploy
on:
push:
branches:
- main
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
type: choice
options:
- dev
- stg
- prd
jobs:
extract_environment:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.extract_environment.outputs.environment }}
env:
EVENT: ${{ github.event_name }}
DEPLOY_ENV: ${{ github.event.inputs.environment }}
steps:
- name: Extract Environment
run: |
if [ ${EVENT} == "workflow_dispatch" ]; then
echo "##[set-output name=environment;]$(echo ${DEPLOY_ENV})"
elif [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "##[set-output name=environment;]$(echo "prd")"
fi
id: extract_environment
semantic_release:
runs-on: ubuntu-latest
outputs:
new_release_published: ${{ steps.semantic.outputs.new_release_published }}
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }}
steps:
- name: Checkout
uses: actions/checkout@v2
- if: github.event_name != 'workflow_dispatch'
name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
id: semantic
with:
semantic_version: 16
extra_plugins: |
conventional-changelog-eslint
branches: |
[
'main'
]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
deploy:
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
needs: [extract_environment, semantic_release]
runs-on:
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
steps:
- name: Printing stats
env:
EVENT: ${{ github.event_name }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ENV: ${{ needs.extract_environment.outputs.environment }}
run: echo ${GITHUB_REF#refs/heads/}
- name: Checkout
uses: actions/checkout@v2
- name: Update Pip
run: |
python3 -m pip install --upgrade pip
- name: Install Docker Compose
run: |
python3 -m pip install docker-compose --upgrade
- name: Install AWS CLI
run: |
python3 -m pip install awscli --upgrade
- name: Install AWS Elastic Beanstalk CLI
run: |
python3 -m pip install awsebcli --upgrade
- name: Configure AWS Region
uses: aws-actions/configure-aws-credentials@v1
id: aws
with:
aws-region: us-east-1
- name: Login to AWS ECR
id: login_ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, Tag, and Push Image to AWS ECR
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: |
docker-compose -f build.docker-compose.yml build
docker-compose -f build.docker-compose.yml push
- name: Create ZIP file to Deploy AWS Beanstalk
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: |
sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
zip deploy.zip docker-compose.yml -r .ebextensions
- name: Deploy AWS Beanstalk
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
AWS_REGION: us-east-1
APP_NAME: ${{ github.event.repository.name }}
run: |
eb use $APP_NAME-$ENV
echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
eb deploy
- name: Remove Docker's Trash
if: always()
run: |
docker system prune --volumes -a -f
docker system df
api_docs:
needs: [extract_environment, semantic_release, deploy]
runs-on:
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Extract Docs PageId
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
DEV_DOCS_PAGE_ID: ${{ secrets.DEV_DOCS_PAGE_ID }}
STG_DOCS_PAGE_ID: ${{ secrets.STG_DOCS_PAGE_ID }}
PRD_DOCS_PAGE_ID: ${{ secrets.PRD_DOCS_PAGE_ID }}
shell: bash
run: |
if [ $ENV == "dev" ]; then
echo "##[set-output name=result;]$(echo $DEV_DOCS_PAGE_ID)"
elif [ $ENV == "stg" ]; then
echo "##[set-output name=result;]$(echo $STG_DOCS_PAGE_ID)"
elif [ $ENV == "prd" ]; then
echo "##[set-output name=result;]$(echo $PRD_DOCS_PAGE_ID)"
fi
id: extract_docs_page_id
- name: Extract Docs BlockId
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
DEV_DOCS_BLOCK_ID: ${{ secrets.DEV_DOCS_BLOCK_ID }}
STG_DOCS_BLOCK_ID: ${{ secrets.STG_DOCS_BLOCK_ID }}
PRD_DOCS_BLOCK_ID: ${{ secrets.PRD_DOCS_BLOCK_ID }}
shell: bash
run: |
if [ $ENV == "dev" ]; then
echo "##[set-output name=result;]$(echo $DEV_DOCS_BLOCK_ID)"
elif [ $ENV == "stg" ]; then
echo "##[set-output name=result;]$(echo $STG_DOCS_BLOCK_ID)"
elif [ $ENV == "prd" ]; then
echo "##[set-output name=result;]$(echo $PRD_DOCS_BLOCK_ID)"
fi
id: extract_docs_block_id
- name: Generate API docs
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
DOCS_URL: ${{ secrets.DOCS_URL }}
DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }}
DOCS_PAGE_ID: ${{ steps.extract_docs_page_id.outputs.result }}
DOCS_BLOCK_ID: ${{ steps.extract_docs_block_id.outputs.result }}
continue-on-error: true
run: |
SWAGGER=`cat swagger.txt`
curl --request POST \
--url $DOCS_URL \
--header 'Content-Type: application/json' \
--data @<(cat <<EOF
{
"service": "$DOCS_API_TOKEN",
"pageId": "$DOCS_PAGE_ID",
"blockId": "$DOCS_BLOCK_ID",
"schema": $SWAGGER
}
EOF
)
- name: Cleanup Docker's Leftovers
if: always()
continue-on-error: true
run: |
docker system prune
docker rmi -f $(docker images -aq)
-150
View File
@@ -1,150 +0,0 @@
name: Deploy
on:
push:
branches:
- alpha
- beta
- main
jobs:
extract_environment:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.extract_environment.outputs.environment }}
steps:
- name: Extract Environment
run: |
if [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "##[set-output name=environment;]$(echo "prd")"
elif [ ${GITHUB_REF} == "refs/heads/alpha" ]; then
echo "##[set-output name=environment;]$(echo "dev")"
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "##[set-output name=environment;]$(echo "stg")"
fi
id: extract_environment
semantic_release:
runs-on: ubuntu-latest
outputs:
new_release_published: ${{ steps.semantic.outputs.new_release_published }}
new_release_version: ${{ steps.semantic.outputs.new_release_version }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
id: semantic
with:
semantic_version: 16
extra_plugins: |
@saithodev/semantic-release-backmerge
conventional-changelog-eslint
branches: |
[
'main',
{
name: 'alpha',
prerelease: true
},
{
name: 'beta',
prerelease: true
}
]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy:
needs: [extract_environment, semantic_release]
runs-on: [self-hosted, "${{ needs.extract_environment.outputs.environment }}"]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Update Pip
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install --upgrade pip
- name: Install Docker Compose
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install docker-compose --upgrade
- name: Install AWS CLI
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install awscli --upgrade
- name: Install AWS Elastic Beanstalk CLI
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install awsebcli --upgrade
- name: Configure AWS Region
if: needs.semantic_release.outputs.new_release_published == 'true'
uses: aws-actions/configure-aws-credentials@v1
id: aws
with:
aws-region: us-east-1
- name: Extract Environment
if: needs.semantic_release.outputs.new_release_published == 'true'
shell: bash
run: |
if [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "##[set-output name=result;]$(echo "prd")"
elif [ ${GITHUB_REF} == "refs/heads/alpha" ]; then
echo "##[set-output name=result;]$(echo "dev")"
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "##[set-output name=result;]$(echo "stg")"
fi
id: extract_environment
- name: Login to AWS ECR
if: needs.semantic_release.outputs.new_release_published == 'true'
id: login_ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, Tag, and Push Image to AWS ECR
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
docker-compose -f build.docker-compose.yml build --build-arg NPM_TOKEN=${NPM_TOKEN}
docker-compose -f build.docker-compose.yml push
- name: Create ZIP file to Deploy AWS Beanstalk
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: |
sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
zip deploy.zip docker-compose.yml -r .ebextensions
- name: Deploy AWS Beanstalk
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
AWS_REGION: us-east-1
APP_NAME: maestro
run: |
eb use $APP_NAME-$ENV
echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
eb deploy
- name: Remove Docker's Trash
continue-on-error: true
run: |
docker system prune
docker rmi -f $(docker images -aq)
+7 -22
View File
@@ -2,8 +2,6 @@ name: Test
on:
pull_request:
branches:
- alpha
- beta
- main
jobs:
@@ -13,34 +11,21 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Docker Compose
run: |
python3 -m pip install docker-compose --upgrade
- name: Build
env:
ENV: test
IMAGE_TAG: test
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
APP_NAME: ${{ github.event.repository.name }}
run: |
export ENV=test
export IMAGE_TAG=test
export ACCOUNT_ID=test
docker-compose -f build.docker-compose.yml build --build-arg NPM_TOKEN=${NPM_TOKEN}
docker build -t $APP_NAME --target test .
- name: Run Test
env:
ENV: test
IMAGE_TAG: test
ACCOUNT_ID: test
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
APP_NAME: ${{ github.event.repository.name }}
run: |
export ENV=test
export IMAGE_TAG=test
docker-compose -f build.docker-compose.yml run -e NPM_TOKEN=${NPM_TOKEN} --rm --entrypoint="npm run test" maestro
docker run --rm --entrypoint="npm" $APP_NAME run test
- name: Remove Docker's Trash
continue-on-error: true
if: always()
run: |
docker system prune
docker rmi -f $(docker images -aq)
docker system prune --volumes -a -f
docker system df
+2 -2
View File
@@ -29,7 +29,7 @@ lerna-debug.log*
# IDE - VSCode
.vscode/*
!.vscode/settings.json
#!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
@@ -37,4 +37,4 @@ lerna-debug.log*
# Ignore .env files
*.env
.env
.env
-1
View File
@@ -1 +0,0 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
+1
View File
@@ -0,0 +1 @@
18.3.0
+6 -19
View File
@@ -5,18 +5,18 @@
{
"preset": "eslint",
"releaseRules": [
{ "tag": "FIX", "release": "patch" },
{ "tag": "BREAKING", "release": "major" },
{ "tag": "FEAT", "release": "minor" },
{ "tag": "UPDATE", "release": "minor" },
{ "tag": "UPGRADE", "release": "minor" },
{ "tag": "BREAKING", "release": "major" },
{ "tag": "DOCS", "release": "patch" },
{ "tag": "CHORE", "release": "minor" },
{ "tag": "BUILD", "release": "patch" },
{ "tag": "REFACTOR", "release": "minor" },
{ "tag": "PERF", "release": "minor" },
{ "tag": "FIX", "release": "patch" },
{ "tag": "DOCS", "release": "patch" },
{ "tag": "BUILD", "release": "patch" },
{ "tag": "TEST", "release": "patch" },
{ "tag": "CI", "release": "patch" },
{ "tag": "PERF", "release": "minor" }
{ "tag": "CI", "release": "patch" }
]
}
],
@@ -26,19 +26,6 @@
"preset": "eslint"
}
],
[
"@saithodev/semantic-release-backmerge",
{
"branches": [
{ "from": "main", "to": "alpha" },
{ "from": "main", "to": "beta" }
],
"backmergeStrategy": "merge",
"clearWorkspace": true,
"restoreWorkspace": true
}
],
"@semantic-release/npm",
"@semantic-release/github"
]
}
+22
View File
@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug NestJs",
"request": "launch",
"runtimeArgs": [
"run",
"start:debug"
],
"runtimeExecutable": "npm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"console": "integratedTerminal"
}
]
}
+16 -11
View File
@@ -1,16 +1,21 @@
FROM node:14.15.4-alpine3.12 as packages
WORKDIR /packages
ARG NPM_TOKEN
COPY package.json .
COPY .npmrc .
RUN npm install
RUN rm -f ./.npmrc
# install all dependencies
FROM node:18.3.0-alpine3.15 as packages
WORKDIR /app
COPY package.json package-lock.json ./
RUN apk add --no-cache aws-cli \
&& aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \
&& npm install
FROM node:14.15.4-alpine3.12
# unit test specific build
FROM node:18.3.0-alpine3.15 as test
WORKDIR /app
COPY --from=packages /app/node_modules ./node_modules
COPY . ./
FROM node:18.3.0-alpine3.15
WORKDIR /app
COPY . /app/
ARG NPM_TOKEN
COPY --from=packages /packages/node_modules /app/node_modules
COPY --from=packages /app/node_modules ./node_modules
RUN npm run build
EXPOSE 3333
ENTRYPOINT npm run start
ENTRYPOINT npm run start
+299 -2
View File
@@ -1,4 +1,301 @@
<p align="center">
<image src="./assets/maestro.svg" style="width:10rem">
<h1 align="center">Maestro</h1>
</p>
</p>
# Maestro
Maestro is the Dadosfera's gateway, it's responsible for the communication between the frontend application and Dadosfera's microservices.
## 🚀 Starting
These instructions will allow you to get a working copy of the project on your local machine for development and testing purposes.
### 📋 Requirements
- [NodeJS v18.3.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
- Request access to AWS Console dev account for **all services** (avoid gradually asking for each needed service. it will slow down your development cycle)
- Create your Access Key on the "Security credentials" menu
- Set the Access Key on your local development machine
- Request access to the dev, stg and prd VPNs
### 🔧 Installation<a id="installation"></a>
- Clone the repository
- SSH
```
git clone git@github.com:dadosfera/maestro.git
```
or
- HTTPS
```
git clone https://github.com/dadosfera/maestro.git
```
- Select the correct node version (optional, only if using [NVM](https://github.com/nvm-sh/nvm)):
```sh
nvm use
```
- Install the project dependencies:
```sh
npm i
```
- Setup the following enviroment variables:
```
ENV=
DUC_URL=
INFACTORY_URL=
OTFACTORY_URL=
PIFACTORY_URL=
SM_OAUTH_PATH=
```
- Start the server:
```sh
# dev mode
npm run start:dev
# or in debug mode
npm run start:debug
```
The service should start successfully.
## Authentication decorators
Maestro have utilities to ease the user authentication on every controller and route. The following decorators are available:
### `@Authenticated`
If the user must be authenticated to make request, we can use the `@Authenticated` decorator in the controller or route, as needed.
```ts
import { Authenticated } from '../../authentication/authentication.decorator';
@Controller('foo')
@Authenticated()
class FooController {
/* ... */
@Get('bar')
async getBar() {
this.logger.info('user is authenticated!');
return { authenticated: true };
}
}
```
```ts
import { Authenticated } from '../../authentication/authentication.decorator';
@Controller('foo')
class FooController {
/* ... */
@Get('bar')
@Authenticated()
async getBar() {
this.logger.info('user is authenticated!');
return { authenticated: true };
}
@Post('bar')
async postBar() {
this.logger.info('user is NOT authenticated!');
return { authenticated: false };
}
}
```
### `@RequireAllPermissions`
This decorator requires that **all permissions** listed are granted to the requesting user.
```ts
import { RequireAllPermissions } from '../../authentication/authentication.decorator';
import { PERMISSIONS } from '../../authentication/permissions.enum';
@Controller('foo')
class FooController {
/* ... */
@Get('bar')
@RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.CREATE)
async getBar() {
/* ... */
}
}
```
### `@RequireSomePermission`
In the following case, the user is required to have **at least one** listed permission.
```ts
import { RequireSomePermission } from '../../authentication/authentication.decorator';
import { PERMISSIONS } from '../../authentication/permissions.enum';
@Controller('foo')
@RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.CREATE)
class FooController {
/* ... */
@Get('bar')
async getBar() {
/* ... */
}
}
```
### `@AuthenticateCondition`
If a more complicated authentication check needs to be done, we can use the `@AuthenticateCondition` decorator to define it. The custom function must return `true` to authenticate the request.
In the following example:
- all routes on the `FooController` controller can only be requested from localhost
- `POST /foo/bar` can only be requested from localhost **and** by users from customer id `111...eef`
```ts
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
@Controller('foo')
// allow requests only from localhost
@AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1')
class FooController {
/* ... */
@Get('bar')
async getBar() {
/* ... */
}
@Post('bar')
// allow requests only from a specific customer
@AuthenticateCondition((request: Request, user: RequestUser) => user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef')
async postBar() {
/* ... */
}
}
```
### Note on authentication decorators
- The old authentication method placed the user data in the `request.body.info` field, this imposes certain issues regarding the request body because this data should be from the frontend without any modification by Maestro. Now this usage is ⚠️ **DEPRECATED** ⚠️. We are working to migrate to the `@User` parameter decorator. The old method is working while the migration is in progress.
- Authentication decorators can be used together and all of them **must** pass to the request be authenticated, but in the general case you don't need to (*and wouldn't like to...*) use all of them together, as you can code all of the authentication logic in the `@AuthenticateCondition` decorator.
```ts
import {
RequireAllPermissions,
RequireSomePermission,
AuthenticateCondition,
} from '../../authentication/authentication.decorator';
import { PERMISSIONS } from '../../authentication/permissions.enum';
import { Waa, Baz } from './authenticationFunctions'
@Controller('foo')
@RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST)
@RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.ADMIN)
@AuthenticateCondition(Waa)
@AuthenticateCondition(Baz)
class FooController {
/* ... */
@Get('bar')
async getBar() {
/* ... */
}
@Post('bar')
@RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.USE, Permissions.BAR.REQUEST)
async postBar() {
/* ... */
}
}
```
- If `@RequireAllPermissions` and `@RequireSomePermission` are used with **only a single permission**, they present the **exactly same behavior**.
```ts
// same behavior
@RequireAllPermissions(Permissions.BAR.MANAGE)
@RequireSomePermission(Permissions.BAR.MANAGE)
```
## `@User` parameter decorator
The requesting user data can be obtained using the @User parameter decorator, like in the following snippet:
```ts
import { User, RequestUser } from '../../authentication/user.decorator';
@Controller('foo')
class FooController {
/* ... */
@Get('bar')
async getBar(@User() user: RequestUser) {
this.logger.info(user);
return { user };
}
}
```
If the user is required to be logged in, set `required` to `true`, as you would want in the `change-password` operation:
```ts
import { User, RequestUser } from '../../authentication/user.decorator';
@Controller('user')
class UserController {
/* ... */
@Post('change-password')
@HttpCode(HttpStatus.OK)
async changePassword(
@User({ required: true }) user: RequestUser,
@Body() body: AuthChangePasswordRequest,
) {
const { oldPassword, newPassword } = body;
return this.authClient.changePassword({
userId: user.user_id,
oldPassword,
newPassword,
});
}
}
```
## 📦 Development
### ⌨️ Coding Style
By default, we use [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) with default settings.
**We recommend using Visual Studio Code and installing the recommended extensions to ease the development process.**
### Commits pattern
Our workflow pipeline follows the conventional commits specs ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) to release versions accordingly.
Format: `<type>[optional scope]: <description>`
Example: `FIX: ensure Range headers adhere more closely to RFC 2616`
### Branching naming convention
- **Feature**: Any code changes for a new module or use case should be done on a feature branch. This branch is created based on the `main` branch. When all changes are done, a Pull Request/Merge Request is needed to put all of these changes back to the `main` branch. Examples: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`.
**It is recommended to use all lower caps letters and hyphen (-) to separate words unless it is a specific item name or ID. Underscore (_) could be used to separate the ID and description.**
- **Bug Fix**: If the code changes made from the feature branch were rejected after a release, sprint or demo, any necessary fixes after that should be done on the bugfix branch. Examples: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`.
- **Hot Fix**: If there is a need to fix a blocker, do a temporary patch, apply a critical framework or configuration change that should be handled immediately, it should be created as a Hotfix. Examples: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`.
- **Experimental**: A branch for playing around. Any new feature or idea that is not part of a release or a sprint. Example: `experimental/dark-theme-support`.
### Making a Pull Request
1. Commit your changes
2. Open the Pull Request on GitHub
3. Send Pull Request link in microsfera Google Chat Group for review and possible approval
## 🛠️ Built with
Some technologies used in this project:
- [NestJS](https://docs.nestjs.com) - Framework for building efficient and scalable NodeJS server-side applications
## ⚙️ Back-end Architecture
The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).
+18
View File
@@ -0,0 +1,18 @@
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
moduleFileExtensions: ['ts', 'js', 'json'],
modulePaths: ['<rootDir>'],
roots: ['<rootDir>/src/', '<rootDir>/test/'],
testRegex: '.*\\.(test|spec)\\.[jt]s$',
transform: { '\\.[jt]s$': 'ts-jest' },
collectCoverageFrom: ['**/*.[jt]s'],
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: [
'<rootDir>/node_modules/',
'.*\\.module\\.[jt]s$',
],
// moduleDirectories: ['node_modules'], // default is already 'node_modules'
// rootDir: '.', //No need
// testEnvironment: 'node', //Defaults to 'node'
};
export default config;
+11 -2
View File
@@ -2,7 +2,16 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": ["**/*.proto"],
"assets": [
"**/*.proto",
{
"include": "i18n/**/*",
"watchAssets": true
}
],
"plugins": [
"@nestjs/swagger"
],
"watchAssets": true
}
}
}
+6673 -5003
View File
File diff suppressed because it is too large Load Diff
+51 -42
View File
@@ -1,11 +1,15 @@
{
"name": "grpc-server-and-client-nestjs-micro-pattern",
"name": "maestro",
"version": "0.0.0",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"engines": {
"node": "18.3.0"
},
"scripts": {
"preinstall": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1",
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
@@ -21,67 +25,72 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@grpc/grpc-js": "^1.5.10",
"@grpc/proto-loader": "^0.6.9",
"@nestjs/common": "^8.4.3",
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.3",
"@dadosfera/protospack-v2": "3.17.5",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/common": "^8.4.7",
"@nestjs/config": "^1.2.1",
"@nestjs/core": "^8.4.3",
"@nestjs/core": "^8.4.7",
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^8.4.3",
"@nestjs/platform-express": "^8.4.3",
"@nestjs/microservices": "^8.4.7",
"@nestjs/passport": "^8.2.2",
"@nestjs/platform-express": "^8.4.7",
"@nestjs/schedule": "^1.1.0",
"@nestjs/swagger": "^5.2.1",
"@victorradael/protospack": "^1.5.5",
"axios": "^0.25.0",
"dotenv": "^14.2.0",
"grpc": "^1.24.11",
"helmet": "^5.0.2",
"axios": "^0.27.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cron-parser": "^4.4.0",
"dotenv": "^14.3.2",
"elastic-apm-node": "^3.36.0",
"helmet": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"jwk-to-pem": "^2.0.5",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-forcedotcom": "^0.1.5",
"passport-google-oauth20": "^2.0.0",
"passport-hubspot-oauth2": "^1.0.3",
"passport-mailchimp": "^1.1.0",
"protospack": "2.5.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"swagger-ui-express": "^4.3.0"
"swagger-ui-express": "^4.4.0"
},
"overrides": {
"multer": "1.4.5-lts.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.4",
"@nestjs/schematics": "^8.0.8",
"@nestjs/testing": "^8.4.3",
"@nestjs/cli": "^8.2.8",
"@nestjs/schematics": "^8.0.11",
"@nestjs/testing": "^8.4.7",
"@types/express": "^4.17.13",
"@types/jest": "27.0.2",
"@types/jsonwebtoken": "^8.5.8",
"@types/jwk-to-pem": "^2.0.1",
"@types/node": "^16.11.26",
"@types/multer": "^1.4.7",
"@types/node": "^16.11.41",
"@types/passport-facebook": "^2.1.11",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-oauth2": "^1.4.11",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"eslint": "^8.12.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"prettier": "^2.6.1",
"nock": "^13.2.7",
"prettier": "^2.7.1",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.7",
"ts-node": "^10.7.0",
"supertest": "^6.2.3",
"ts-jest": "^27.1.5",
"ts-loader": "^9.3.1",
"ts-node": "^10.8.1",
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
+47 -96
View File
@@ -1,107 +1,58 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { Module } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AuthenticationGuard } from './authentication/authentication.guard';
import { APP_GUARD } from '@nestjs/core';
import { UsersModule } from './modules/users/users.module';
import { RolesModule } from './modules/roles/roles.module';
import { ConnectorModule } from './modules/connector/connector.module';
import { AuthModule } from './modules/auth/auth.module';
import { PermissionsModule } from './modules/permissions/permissions.module';
import { TermsOfUseModule } from './modules/termsOfUse/termsOfUse.module';
import { ConnectionModule } from './modules/connection/connection.module';
import { ConnectionTestModule } from './modules/connection-test/connection-test.module';
import { NetworkConfigModule } from './modules/network-config/network-config.module';
import { InputsModule } from './modules/inputs/inputs.module';
import { OauthModule } from './modules/oauth/oauth.module';
import { PipelinesModule } from './modules/pipelines/pipelines.module';
import { TransformationsModule } from './modules/transformations/transformations.module';
import { HealthModule } from './modules/health/health.module';
import { CatalogModule } from './modules/catalog/catalog.module';
import { ConfigModule } from '@nestjs/config';
import { LoggerMiddleware } from './middlewares/authentication';
import { InputsController } from './modules/inputs/inputs.controller';
import { TransformationsController } from './modules/transformations/transformations.controller';
import { OutputsController } from './modules/outputs/outputs.controllers';
import { PipelinesController } from './modules/pipelines/pipelines.controller';
import { AuthController } from './modules/auth/auth.controller';
import { HealthController } from './modules/health/health.controller';
import { InputsService } from './modules/inputs/inputs.service';
import { TransformationsService } from './modules/transformations/transformations.service';
import { OutputsService } from './modules/outputs/outputs.service';
import { PipelinesService } from './modules/pipelines/pipelines.service';
// import { AuthService } from './modules/auth/auth.service';
import { HealthService } from './modules/health/health.service';
import { InputsClientService } from './clients/inputs/client.service';
import { TransformationsClientService } from './clients/transformations/client.service';
import { OutputsClientService } from './clients/outputs/client.service';
import { PipelinesClientService } from './clients/pipelines/client.service';
import { AuthClientService } from './clients/auth/client.service';
import { OutputsClientConfiguration } from './clients/outputs/client.config';
import { TransformationsClientConfiguration } from './clients/transformations/client.config';
import { AuthClient } from './clients/auth/client.config';
import { InputsClientConfiguration } from './clients/inputs/client.config';
import { PipelinesClientConfiguration } from './clients/pipelines/client.config';
import { CatalogController } from './modules/catalog/catalog.controller';
import { CatalogService } from './modules/catalog/catalog.service';
const authClient = new AuthClient();
const inputClient = new InputsClientConfiguration();
const outputClient = new OutputsClientConfiguration();
const pipelineClient = new PipelinesClientConfiguration();
const transformationClient = new TransformationsClientConfiguration();
import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
@Module({
controllers: [
InputsController,
TransformationsController,
OutputsController,
PipelinesController,
AuthController,
HealthController,
CatalogController,
],
controllers: [],
providers: [
InputsService,
TransformationsService,
OutputsService,
PipelinesService,
// AuthService,
HealthService,
InputsClientService,
TransformationsClientService,
OutputsClientService,
PipelinesClientService,
AuthClientService,
CatalogService,
DadosferaLogger,
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
],
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
ClientsModule.register([
{
name: 'INPUTS_PACKAGE',
...inputClient.config(),
},
{
name: 'TRANSFORMATIONS_PACKAGE',
...transformationClient.config(),
},
{
name: 'OUTPUTS_PACKAGE',
...outputClient.config(),
},
{
name: 'AUTH_PACKAGE',
...authClient.config(),
},
{
name: 'PIPELINES_PACKAGE',
...pipelineClient.config(),
},
]),
ConnectorModule,
AuthModule,
PermissionsModule,
TermsOfUseModule,
ConnectionModule,
NetworkConfigModule,
ConnectionTestModule,
PipelinesModule,
TransformationsModule,
HealthModule,
CatalogModule,
UsersModule,
RolesModule,
InputsModule,
OauthModule,
PipelinesV2Module,
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(
InputsController,
TransformationsController,
OutputsController,
PipelinesController,
CatalogController,
);
}
}
export class AppModule {}
@@ -0,0 +1,56 @@
import { Request } from 'express';
import { CustomDecorator } from '@nestjs/common';
import { PermissionsObjectPermission } from './permissions.enum';
import { RequestUser } from './user.decorator';
export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__';
export type AuthenticationFunction = (req: Request, user: any) => boolean;
// implementation copied from SetMetadata, but tweaked to get existing values
// of the metadataKey and accumulate it with the new metadataValue
function SetMultipleMetadata(metadataKey, metadataValue): CustomDecorator {
const decoratorFactory = (target, key, descriptor) => {
// .start: tweak
// descriptor?.value = function decorator; target = class decorator
const accumulatedVal =
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
accumulatedVal.push(metadataValue);
// .end: tweak
if (descriptor) {
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
return descriptor;
}
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
return target;
};
decoratorFactory.KEY = metadataKey;
return decoratorFactory as any;
}
export function RequireAllPermissions(
...permissions: PermissionsObjectPermission[]
) {
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
);
}
export function RequireSomePermission(
...permissions: PermissionsObjectPermission[]
) {
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
permissions.some(({ seqid }) => user.permissions.includes(seqid)),
);
}
export function AuthenticateCondition(func: AuthenticationFunction) {
return SetMultipleMetadata(AUTH_FUNCTION_KEY, func);
}
export function Authenticated() {
return SetMultipleMetadata(AUTH_FUNCTION_KEY, () => true);
}
@@ -0,0 +1,457 @@
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import jwt from 'jsonwebtoken';
import { Body, Controller, Get } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import {
AuthenticateCondition,
Authenticated,
RequireAllPermissions,
RequireSomePermission,
} from './authentication.decorator';
import { AuthenticationGuard } from './authentication.guard';
import { PERMISSIONS_GROUPS } from './permissions.enum';
import { AuthClientService } from '../modules/auth/auth.service';
import ErrorCodes from '../utils/errorCodes';
const logger = {
info: (...args) => args,
error: (...args) => args,
};
@Controller('no-class-auth')
class NoClassAuthController {
@Get('body')
async getBody(@Body() body) {
return { body };
}
@Get('authenticated')
@Authenticated()
async mustBeAuthenticated(@Body() body) {
return { body };
}
@Get('required-permission')
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
async requiredPermission(@Body() body) {
return { body };
}
@Get('has-all-permissions')
@RequireAllPermissions(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
)
async hasAllPermissions(@Body() body) {
return { body };
}
@Get('has-some-permission')
@RequireSomePermission(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
)
async hasSomePermission(@Body() body) {
return { body };
}
}
@Controller('class-auth-condition')
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
@RequireSomePermission(PERMISSIONS_GROUPS.METABASE.permissions.OPEN)
class ClassAuthConditionController {
@Get('body')
async getBody(@Body() body) {
return { body };
}
@Get('authenticated')
@Authenticated()
async mustBeAuthenticated(@Body() body) {
return { body };
}
@Get('required-permission')
@AuthenticateCondition((req) => req.get('x-on-route') === 'ok')
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
async requiredPermission(@Body() body) {
return { body };
}
}
describe('authentication.guard', () => {
let app: INestApplication;
const jwtSecretA = {
kid: 'token-a-testing-shared-key',
pem: '$tr0ng-SH4Red-secr3t!!!~gl0ba1~]',
};
const jwtSecretB = {
kid: 'token-b',
pem: 'another-shared-token',
};
const fakeUserPayload = {
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
username: 'super.admin',
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
customer_name: 'dadosfera',
customer_tier: 'BASIC',
};
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: AuthClientService,
useValue: {
getPublicKeys: async () => ({
keys: [jwtSecretA, jwtSecretB],
}),
},
},
{
provide: DadosferaLogger,
useValue: { logger },
},
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
],
controllers: [NoClassAuthController, ClassAuthConditionController],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
function AssertBodyNoAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({ body: {} });
}
function AssertBodyWithAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({
body: {
info: {
customer: 'dadosfera',
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
customer_tier: 'BASIC',
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
},
},
});
}
function AssertUnauthorized(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.UNAUTHORIZED);
expect(res.body).toStrictEqual({
code: ErrorCodes.AUTH.UNAUTHORIZED,
error: 'Não autenticado',
message: 'É necessário estar logado para realizar essa operação',
statusCode: HttpStatus.UNAUTHORIZED,
});
}
function AssertForbidden(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.FORBIDDEN);
expect(res.body).toStrictEqual({
code: ErrorCodes.AUTH.FORBIDDEN,
error: 'Não autorizado',
message:
'Você não tem permissões suficientes para realizar essa operação',
statusCode: HttpStatus.FORBIDDEN,
});
}
function NoClassAuthTest(accessToken: string, tokenName: string[]) {
const route = '/no-class-auth';
describe(`${route}, ${tokenName?.length ? tokenName : 'none'} auth`, () => {
it('should GET /body with user data (if available)', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/body`)
.set({ Authorization: accessToken });
if (tokenName?.length) {
AssertBodyWithAuth(res);
} else {
AssertBodyNoAuth(res);
}
});
it('should GET /authenticated if authenticated', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/authenticated`)
.set({ Authorization: accessToken });
if (tokenName?.length) {
AssertBodyWithAuth(res);
} else {
AssertUnauthorized(res);
}
});
it('should GET /required-permission if authenticated', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/required-permission`)
.set({ Authorization: accessToken });
// zendesk required
if (tokenName?.includes('zendesk')) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should GET /has-all-permissions if authenticated', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/has-all-permissions`)
.set({ Authorization: accessToken });
// zendesk and metabase
if (tokenName?.includes('zendesk') && tokenName?.includes('metabase')) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should GET /has-some-permission if authenticated', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/has-some-permission`)
.set({ Authorization: accessToken });
// zendesk or metabase
if (tokenName?.includes('zendesk') || tokenName?.includes('metabase')) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
});
}
function ClassAuthConditionTest(accessToken: string, tokenName: string[]) {
const route = '/class-auth-condition';
describe(`${route}, ${tokenName?.length ? tokenName : 'none'} auth`, () => {
describe('GET /body', () => {
it('should GET /body if authenticated and with correct headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/body`)
.set({
'x-on-class': 'ok',
Authorization: accessToken,
});
// metabase required
if (tokenName?.includes('metabase')) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should not GET /body without correct headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/body`)
.set({ Authorization: accessToken });
if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
});
describe('GET /authenticated', () => {
it('should GET /authenticated if authenticated and with correct headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/authenticated`)
.set({
'x-on-class': 'ok',
Authorization: accessToken,
});
// metabase required
if (tokenName?.includes('metabase')) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should not GET /authenticated without correct class headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/authenticated`)
.set({ Authorization: accessToken });
if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
});
describe('GET /required-permission', () => {
it('should GET /required-permission if authenticated and with correct headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/required-permission`)
.set({
'x-on-class': 'ok',
'x-on-route': 'ok',
Authorization: accessToken,
});
// zendesk and metabase
if (
tokenName?.includes('zendesk') &&
tokenName?.includes('metabase')
) {
AssertBodyWithAuth(res);
} else if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should not GET /required-permission without correct route headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/required-permission`)
.set({
'x-on-class': 'ok',
Authorization: accessToken,
});
if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
it('should not GET /required-permission without correct class headers', async () => {
const res = await request(app.getHttpServer())
.get(`${route}/required-permission`)
.set({
'x-on-route': 'ok',
Authorization: accessToken,
});
if (tokenName?.length) {
AssertForbidden(res);
} else {
AssertUnauthorized(res);
}
});
});
});
}
describe('token validation', () => {
it('should accept token kid A', async () => {
const Authorization = CreateToken([], {
jwt: {
kid: jwtSecretA.kid,
pem: jwtSecretA.pem,
},
});
const res = await request(app.getHttpServer())
.get('/no-class-auth/authenticated')
.set({
Authorization,
});
AssertBodyWithAuth(res);
});
it('should accept token kid B', async () => {
const Authorization = CreateToken([], {
jwt: {
kid: jwtSecretB.kid,
pem: jwtSecretB.pem,
},
});
const res = await request(app.getHttpServer())
.get('/no-class-auth/authenticated')
.set({
Authorization,
});
AssertBodyWithAuth(res);
});
it('should not accept unknown kid', async () => {
const Authorization = CreateToken([], {
jwt: {
kid: 'unknown-key-id',
pem: jwtSecretA.pem,
},
});
const res = await request(app.getHttpServer())
.get('/no-class-auth/authenticated')
.set({
Authorization,
});
AssertUnauthorized(res);
});
});
function CreateToken(permissions, overrides?: { user?: any; jwt?: any }) {
const tokenPayload = {
...fakeUserPayload,
permissions: permissions.map(({ seqid }) => seqid),
token_use: 'access',
...overrides?.user,
};
return jwt.sign(tokenPayload, overrides?.jwt?.pem ?? jwtSecretA.pem, {
keyid: overrides?.jwt?.kid ?? jwtSecretA.kid,
});
}
NoClassAuthTest(null, null);
ClassAuthConditionTest(null, null);
const tokenZ = CreateToken([PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN]);
NoClassAuthTest(tokenZ, ['zendesk']);
ClassAuthConditionTest(tokenZ, ['zendesk']);
const tokenM = CreateToken([PERMISSIONS_GROUPS.METABASE.permissions.OPEN]);
NoClassAuthTest(tokenM, ['metabase']);
ClassAuthConditionTest(tokenM, ['metabase']);
const tokenZM = CreateToken([
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
]);
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
});
+136
View File
@@ -0,0 +1,136 @@
import {
Injectable,
CanActivate,
OnApplicationBootstrap,
ExecutionContext,
Inject,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import assert from 'assert';
import jwt from 'jsonwebtoken';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AuthClientService } from '../modules/auth/auth.service';
import {
AuthenticationFunction,
AUTH_FUNCTION_KEY,
} from './authentication.decorator';
import { RequestUser } from './user.decorator';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
@Injectable()
export class AuthenticationGuard
implements CanActivate, OnApplicationBootstrap
{
logger: DadosferaLogger;
pems: Map<string, string>;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private reflector: Reflector,
private authClient: AuthClientService,
) {
this.pems = new Map();
this.logger = dadosferaLogger.logger;
}
async onApplicationBootstrap() {
return this.loadDucJWKS();
}
private async loadDucJWKS() {
const { keys } = await this.authClient.getPublicKeys();
keys.forEach((key) => {
this.pems.set(key.kid, key.pem);
});
}
canActivate(ctx: ExecutionContext): boolean {
const authFunctions = this.reflector.getAllAndMerge<
AuthenticationFunction[]
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
const mustBeAuthenticated = authFunctions.length > 0;
const request = ctx.switchToHttp().getRequest();
const accessToken = this.validateToken(request, mustBeAuthenticated);
if (!mustBeAuthenticated) {
// no need to be authenticated
return true;
}
if (!accessToken) {
// couldn't load valid token
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
}
// every authentication function must return true to authenticate
if (!authFunctions.every((func) => func(request, accessToken))) {
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
}
return true;
}
private validateToken(
request,
mustBeAuthenticated: boolean,
): RequestUser | false {
const accessToken = request.get('Authorization');
let accessTokenPayload: RequestUser;
// If the user isn't authenticated, an error will occurr anywhere here.
// Fancy error avoidance isn't performed by purpose, such as avoiding to access null values.
try {
const accessTokenDecoded: any = jwt.decode(accessToken, {
complete: true,
});
/*assert(
accessTokenDecoded.payload.iss === 'duc',
'token should be issued by DUC',
);*/
assert(
accessTokenDecoded?.payload?.token_use === 'access',
'should be an access token',
);
const { kid } = accessTokenDecoded.header;
const pemValue: string = this.pems.get(kid);
jwt.verify(accessToken, pemValue);
accessTokenPayload = accessTokenDecoded.payload;
} catch (err) {
// log errors if authentication is required
if (mustBeAuthenticated) {
this.logger.error('Unable to verify duc access token\n' + err?.stack);
}
return false;
}
request.accessTokenPayload = accessTokenPayload;
request.user = {
user_id: accessTokenPayload.user_id,
username: accessTokenPayload.username,
permissions: accessTokenPayload.permissions,
customer_id: accessTokenPayload.customer_id,
customer_name: accessTokenPayload.customer_name,
customer_tier: accessTokenPayload.customer_tier,
access_token: accessToken,
};
// TODO: for backwards compatibility. remove in the future
request.body.info = {
user_id: accessTokenPayload.user_id,
customer_id: accessTokenPayload.customer_id,
customer: accessTokenPayload.customer_name,
customer_tier: accessTokenPayload.customer_tier,
};
return accessTokenPayload;
}
}
+539
View File
@@ -0,0 +1,539 @@
import { Logger } from '@nestjs/common';
import assert from 'assert';
import { LanguageEnum } from 'src/utils/languages.enum';
const logger = new Logger();
export enum PermissionUsages {
PUBLIC = 'public',
INTERNAL = 'internal',
}
export interface PermissionsObjectPermission {
seqid: number;
claim: string;
usage: PermissionUsages;
name?: Record<LanguageEnum, string>;
}
export interface PermissionsObjectPermissionTranslated {
seqid: number;
claim: string;
usage: PermissionUsages;
name?: string;
}
export interface PermissionsObjectGroup {
title: {
'pt-br': string;
'en-us': string;
};
permissions: Record<string, PermissionsObjectPermission>;
}
/*
- like field numbers in gRPC, avoid to change (or reuse previously used) seqids once its deployed to DUC
- it is possible to update claim and usage, not seqid
*/
/*export const Permissions: {
[P in keyof any]: { [Q in keyof any]: Permission };
} = {*/
export const PERMISSIONS_GROUPS = {
AUTH: {
title: {
'pt-br': 'Autenticação',
'en-us': 'Authentication',
'es-es': 'Authentication',
},
permissions: {
CREATE: {
seqid: 18,
claim: 'POST /auth',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': '',
'en-us': '',
'es-es': '',
},
},
},
},
PIPELINE: {
title: {
'pt-br': 'Coletar | Pipelines',
'en-us': 'Collect | Pipelines',
'es-es': 'Colecta | Pipelines',
},
permissions: {
CREATE: {
seqid: 23,
claim: 'POST /pipelines',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Criar pipelines',
'en-us': 'Create pipelines',
'es-es': 'Crear pipelines',
},
},
GET: {
seqid: 13,
claim: 'GET /pipelines',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Buscar pipelines',
'en-us': 'Get pipelines',
'es-es': 'Obtener pipelines',
},
},
UPDATE: {
seqid: 29,
claim: 'PUT /pipelines',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Atualizar pipeline',
'en-us': 'Update pipeline',
'es-es': 'Actualizar pipeline',
},
},
DELETE: {
seqid: 5,
claim: 'DELETE /pipelines',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Excluir pipeline',
'en-us': 'Delete pipeline',
'es-es': 'Eliminar pipeline',
},
},
},
},
CONNECTION: {
title: {
'pt-br': 'Coletar | Fontes de dados',
'en-us': 'Collect | Sources',
'es-es': 'Colecta | Sources',
},
permissions: {
CREATE: {
seqid: 37,
claim: 'connection:create',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Cadastrar Fonte',
'en-us': 'Create Source',
'es-es': 'Crear Source',
},
},
DELETE: {
seqid: 38,
claim: 'connection:delete',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Excluir Fonte',
'en-us': 'Remove Source',
'es-s': 'Eliminar Source',
},
},
UPDATE: {
seqid: 39,
claim: 'connection:update',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Editar Fonte',
'en-us': 'Edit Source',
'es-es': 'Editar Source',
},
},
},
},
NETWORK_CONFIG: {
title: {
'pt-br': 'Coletar | Redes',
'en-us': 'Collect | Network',
'es-es': 'Colecta | Network',
},
permissions: {
CREATE: {
seqid: 35,
claim: 'network_config:create',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Cadastrar VPN',
'en-us': 'Create VPN',
'es-es': 'Crear VPN',
},
},
DELETE: {
seqid: 36,
claim: 'network_config:delete',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Excluir VPN',
'en-us': 'Remove VPN',
'es-es': 'Eliminar VPN',
},
},
},
},
OUTPUT: {
title: {
'pt-br': 'Output',
'en-us': 'Output',
'es-es': 'Output',
},
permissions: {
CREATE: {
seqid: 22,
claim: 'POST /outputs',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Criar outputs',
'en-us': 'Create outputs',
'es-es': 'Crear outputs',
},
},
GET: {
seqid: 12,
claim: 'GET /outputs',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Buscar outputs',
'en-us': 'Get outputs',
'es-es': 'Obtener outputs',
},
},
UPDATE: {
seqid: 28,
claim: 'PUT /outputs',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Editar outputs',
'en-us': 'Edit outputs',
'es-es': 'Editar outputs',
},
},
DELETE: {
seqid: 4,
claim: 'DELETE /outputs',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Excluir outputs',
'en-us': 'Remove outputs',
'es-es': 'Eliminar outputs',
},
},
},
},
TRANSFORMATIONS: {
title: {
'pt-br': 'Micro-transformações',
'en-us': 'Micro-transformations',
'es-es': 'Micro-transformaciones',
},
permissions: {
CREATE: {
seqid: 24,
claim: 'POST /transformations',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Criar micro-transformações',
'en-us': 'Create micro-transformations',
'es-es': 'Crear micro-transformaciones',
},
},
GET: {
seqid: 15,
claim: 'GET /transformations',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Buscar micro-transformações',
'en-us': 'Get micro-transformations',
'es-es': 'Obtener micro-transformaciones',
},
},
UPDATE: {
seqid: 30,
claim: 'PUT /transformations',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Editar micro-transformações',
'en-us': 'Edit micro-transformations',
'es-es': 'Editar micro-transformaciones',
},
},
DELETE: {
seqid: 6,
claim: 'DELETE /transformations',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Excluir micro-transformações',
'en-us': 'Remove micro-transformations',
'es-es': 'Eliminar micro-transformaciones',
},
},
},
},
CATALOG: {
title: {
'pt-br': 'Explorar | Catálogo',
'en-us': 'Explore | Catalog',
'es-es': 'Explorar | Catalogar',
},
permissions: {
GET: {
seqid: 7,
claim: 'GET /catalog',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Buscar data assets',
'en-us': 'Search for data assets',
'es-es': 'Buscar data assets',
},
},
CREATE: {
seqid: 19,
claim: 'POST /catalog',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Criar atributos no catálogo',
'en-us': 'Create attributes',
'es-es': 'Crear atributos',
},
},
UPDATE: {
seqid: 25,
claim: 'PUT /catalog',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Editar atributos no catálogo',
'en-us': 'Edit attributes',
'es-es': 'Editar atributos',
},
},
DELETE: {
seqid: 1,
claim: 'DELETE /catalog',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Excluir atributos no catálogo',
'en-us': 'Remove attributes',
'es-es': 'Eliminar atributos',
},
},
},
},
CONNECTORS: {
title: {
'pt-br': 'Conectores',
'en-us': 'Connectors',
'es-es': 'Conectores',
},
permissions: {
CREATE: {
seqid: 20,
claim: 'POST /connectors',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Criar conectores',
'en-us': 'Create connectors',
'es-es': 'Crear conectores',
},
},
GET: {
seqid: 8,
claim: 'GET /connectors',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Buscar conectores',
'en-us': 'Get connectors',
'es-es': 'Obtener conectores',
},
},
UPDATE: {
seqid: 26,
claim: 'PUT /connectors',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Editar conectores',
'en-us': 'Edit connectors',
'es-es': 'Editar conectores',
},
},
DELETE: {
seqid: 2,
claim: 'DELETE /connectors',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Excluir conectores',
'en-us': 'Remove connectors',
'es-es': 'Eliminar conectores',
},
},
},
},
SNOWFLAKE: {
title: {
'pt-br': 'Explorar | Consolidar',
'en-us': 'Explore | Data consolidation',
'es-es': 'Explorar | Consolidación de datos',
},
permissions: {
OPEN: {
seqid: 14,
claim: 'GET /snowflake',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Acessar Snowflake',
'en-us': 'Access Snowflake',
'es-es': 'Acceso Snowflake',
},
},
},
},
ZENDESK: {
title: {
'pt-br': 'Zendesk',
'en-us': 'Zendesk',
'es-es': 'Zendesk',
},
permissions: {
OPEN: {
seqid: 16,
claim: 'GET /zendesk',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Acessar Zendesk',
'en-us': 'Access Zendesk',
'es-es': 'Acceso Zendesk',
},
},
},
},
METABASE: {
title: {
'pt-br': 'Analisar | Visualização',
'en-us': 'Analyze | Visualization',
'es-es': 'Analizar | Visualización',
},
permissions: {
OPEN: {
seqid: 11,
claim: 'GET /metabase',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Acessar Metabase',
'en-us': 'Access Metabase',
'es-es': 'Acceso Metabase',
},
},
},
},
ORCHEST: {
title: {
'pt-br': 'Analisar | Inteligência',
'en-us': 'Analyze | Intelligence',
'es-es': 'Analizar | Inteligencia',
},
permissions: {
INTELLIGENCE: {
seqid: 31,
claim: 'intelligence:open',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Acessar Orchest',
'en-us': 'Access Orchest',
'es-s': 'Acceso Orchest',
},
},
APP_BUILDER: {
seqid: 32,
claim: 'app-builder:open',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Acessar App Builder',
'en-us': 'Access App Builder',
'es-es': 'Acceso App Builder',
},
},
MACHINE_LEARNING: {
seqid: 33,
claim: 'machine-learning:open',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Acessar Machine Learning',
'en-us': 'Access Machine Learning',
'es-es': 'Acceso Machine Learning',
},
},
},
},
USERS: {
title: {
'pt-br': 'Usuários',
'en-us': 'Users',
'es-es': 'Usuarios',
},
permissions: {
ADMIN: {
seqid: 34,
claim: 'users:admin',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Gestão de usuários e grupos',
'en-us': 'Manage user and roles',
'es-es': 'Administrar usuarios y roles',
},
},
},
},
DADOSFERA: {
title: {
'pt-br': 'Usuários',
'en-us': 'Users',
'es-es': 'Usuarios',
},
permissions: {
ADMIN: {
seqid: 40,
claim: 'dadosfera:connections-migration',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'connections-migration',
'en-us': 'connections-migration',
'es-es': 'connections-migration',
},
},
},
},
};
// traverses the object searching for duplicate seqids or claims (executes at runtime)
let nextAvailableSeqid = 0;
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
Object.values(namespace.permissions).map(({ seqid }) => seqid),
);
const claims = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
Object.values(namespace.permissions).map(({ claim }) => claim),
);
Object.values(PERMISSIONS_GROUPS).map((namespace) =>
Object.values(namespace.permissions).map(({ seqid, claim }) => {
const seqidsCount = seqids.filter((x) => x === seqid).length;
assert(seqidsCount === 1, `seqid ${seqid} count is not 1`);
const claimsCount = claims.filter((x) => x === claim).length;
assert(claimsCount === 1, `claim '${claim}' count is not 1`);
nextAvailableSeqid = Math.max(nextAvailableSeqid, seqid);
}),
);
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
+182
View File
@@ -0,0 +1,182 @@
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import jwt from 'jsonwebtoken';
import { Controller, Get } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AuthenticationGuard } from './authentication.guard';
import { AuthClientService } from '../modules/auth/auth.service';
import ErrorCodes from '../utils/errorCodes';
import { User } from './user.decorator';
import { PERMISSIONS_GROUPS } from './permissions.enum';
const logger = {
info: (...args) => args,
error: (...args) => args,
};
@Controller('user')
class UserController {
@Get()
public getUser(@User() user) {
return { user };
}
@Get('options')
public getUserWithOptions(@User({}) user) {
return { user };
}
@Get('not-required')
public getUserNotRequired(@User({ required: false }) user) {
return { user };
}
@Get('required')
public getUserRequired(@User({ required: true }) user) {
return { user };
}
}
describe('user.decorator', () => {
let app: INestApplication;
const jwt_secret = {
kid: 'token-testing-shared-key',
pem: '$tr0ng-SH4Red-secr3t!!!~gl0ba1~]',
};
const fakeUserPayload = {
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
username: 'super.admin',
permissions: [PERMISSIONS_GROUPS.METABASE.permissions.OPEN].map(
({ seqid }) => seqid,
),
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
customer_name: 'dadosfera',
customer_tier: 'BASIC',
access_token: '',
};
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: AuthClientService,
useValue: {
getPublicKeys: async () => ({
keys: [jwt_secret],
}),
},
},
{
provide: DadosferaLogger,
useValue: { logger },
},
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
],
controllers: [UserController],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
function AssertNoAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({});
}
function AssertWithAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({ user: fakeUserPayload });
}
function AssertRequiredNoAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.UNAUTHORIZED);
expect(res.body).toStrictEqual({
code: ErrorCodes.AUTH.UNAUTHORIZED,
error: 'Não autenticado',
message: 'É necessário estar logado para realizar essa operação',
statusCode: HttpStatus.UNAUTHORIZED,
});
}
function UserTest(accessToken: string) {
describe(`with${accessToken ? '' : 'out'} token`, () => {
it('should GET /user', async () => {
const res = await request(app.getHttpServer())
.get('/user')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/options', async () => {
const res = await request(app.getHttpServer())
.get('/user/options')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/not-required', async () => {
const res = await request(app.getHttpServer())
.get('/user/not-required')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/required if logged', async () => {
const res = await request(app.getHttpServer())
.get('/user/required')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertRequiredNoAuth(res);
}
});
});
}
function CreateToken(overrides?) {
const tokenPayload = {
...fakeUserPayload,
token_use: 'access',
...overrides?.user,
};
return jwt.sign(tokenPayload, jwt_secret.pem, {
keyid: jwt_secret.kid,
...overrides?.jwt,
});
}
UserTest(null);
const token = CreateToken();
fakeUserPayload.access_token = token;
UserTest(token);
});
+24
View File
@@ -0,0 +1,24 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
export interface RequestUser {
user_id: string;
username: string;
permissions: number[];
customer_id: string;
customer_name: string;
customer_tier: string;
access_token: string;
}
export const User: (options?: { required?: boolean }) => ParameterDecorator =
createParamDecorator((options: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if (!request.user && options?.required) {
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
}
return request.user;
});
-23
View File
@@ -1,23 +0,0 @@
import { credentials } from '@grpc/grpc-js';
import { ClientOptions, Transport } from '@nestjs/microservices';
import { DucProtoFilePath, DucPackages } from '@victorradael/protospack';
export class AuthClient {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.DUC_URL,
package: DucPackages,
credentials: credentials.createSsl(),
protoPath: DucProtoFilePath,
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
-46
View File
@@ -1,46 +0,0 @@
import { OnModuleInit, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import {
AuthServiceInterface,
DucServicesNames,
} from '@victorradael/protospack';
import { ILogin } from './interfaces';
export class AuthClientService implements OnModuleInit {
private authService: AuthServiceInterface;
constructor(
@Inject('AUTH_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.authService = this.grpcClient.getService<AuthServiceInterface>(
DucServicesNames.AuthProtoService,
);
}
async signIn({ username, password }: ILogin): Promise<any> {
console.log('AuthClientService', 'SignIn');
const tokens = await new Promise((resolve, reject) => {
this.authService.signIn({ username, password }).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return tokens;
}
}
-4
View File
@@ -1,4 +0,0 @@
export interface ILogin {
username: string;
password: string;
}
-23
View File
@@ -1,23 +0,0 @@
import { credentials } from '@grpc/grpc-js';
import { ClientOptions, Transport } from '@nestjs/microservices';
import { InputPackages, InputProtoFilePath } from '@victorradael/protospack';
export class InputsClientConfiguration {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: InputPackages,
credentials: credentials.createSsl(),
protoPath: InputProtoFilePath,
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
-173
View File
@@ -1,173 +0,0 @@
import { OnModuleInit, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { InputService } from '@victorradael/protospack';
import {
objectCamelToSnake,
objectSnakeToCamel,
} from 'src/utils/CaseConverter';
import {
ICreateInputRequest,
IIdRequest,
ITestConnectionRequest,
UpdateInputRequest,
} from './interfaces';
export class InputsClientService implements OnModuleInit {
private inputService: InputService;
constructor(
@Inject('INPUTS_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.inputService =
this.grpcClient.getService<InputService>('InputService');
}
async create(createInputDto: ICreateInputRequest) {
console.log('InputClientService', 'Create');
const createInputResponse = await new Promise((resolve, reject) => {
this.inputService.Create(objectSnakeToCamel(createInputDto)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return createInputResponse;
}
async findOne(data: IIdRequest) {
console.log('InputClientService', 'FindOne');
const findOneInputResponse = await new Promise((resolve, reject) => {
this.inputService.FindOne(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return findOneInputResponse;
}
async findAll(data) {
console.log('InputClientService', 'FindAll');
const findAllInputResponse = await new Promise((resolve, reject) => {
this.inputService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return findAllInputResponse;
}
async update(updateInputDTO: UpdateInputRequest) {
console.log('InputClientService', 'Update');
const updateInputResponse = await new Promise((resolve, reject) => {
this.inputService.Update(objectSnakeToCamel(updateInputDTO)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return updateInputResponse;
}
async remove(idRequest: IIdRequest) {
console.log('InputClientService', 'Remove');
const removeInputResponse = await new Promise((resolve, reject) => {
this.inputService.Remove(objectSnakeToCamel(idRequest)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return removeInputResponse;
}
async testConnection(data: ITestConnectionRequest) {
console.log('InputClientService', 'TestConnection');
const testConnectionResponse = await new Promise((resolve, reject) => {
this.inputService.TestConnection(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return testConnectionResponse;
}
}
-22
View File
@@ -1,22 +0,0 @@
import { OutputPackages, OutputProtoFilePath } from '@victorradael/protospack';
import { ClientOptions, Transport } from '@nestjs/microservices';
import { credentials } from '@grpc/grpc-js';
export class OutputsClientConfiguration {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.OTFACTORY_URL,
package: OutputPackages,
credentials: credentials.createSsl(),
protoPath: OutputProtoFilePath,
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
-160
View File
@@ -1,160 +0,0 @@
import { Controller, Inject, OnModuleInit } from '@nestjs/common';
import { ClientGrpc, Payload } from '@nestjs/microservices';
import {
OutputServicesNames,
OutputsServiceInterface,
} from '@victorradael/protospack';
import {
objectCamelToSnake,
objectSnakeToCamel,
} from 'src/utils/CaseConverter';
import {
ICreateOutputRequest,
IIdRequest,
IOutputUpdateRequest,
} from './interfaces';
@Controller('output')
export class OutputsClientService implements OnModuleInit {
private outputService: OutputsServiceInterface;
constructor(
@Inject('OUTPUTS_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.outputService = this.grpcClient.getService<OutputsServiceInterface>(
OutputServicesNames.OutputService,
);
}
async create(@Payload() createOutputDto: ICreateOutputRequest) {
console.log('OutputClientService', 'Create');
const createOutputResponse = await new Promise((resolve, reject) => {
this.outputService.Create(objectSnakeToCamel(createOutputDto)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const convertedItem = objectCamelToSnake(createOutputResponse['output']);
createOutputResponse['output'] = convertedItem;
return createOutputResponse;
}
async findAll(data) {
console.log('OutputClientService', 'FindAll');
const findAllOutputResponse = await new Promise((resolve, reject) => {
this.outputService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const convertedOutputs = findAllOutputResponse['outputs'].map((ot) => {
return objectCamelToSnake(ot);
});
return { outputs: convertedOutputs };
}
async findOne(data: IIdRequest) {
console.log('OutputClientService', 'FindOne');
const findOneOutputResponse = await new Promise((resolve, reject) => {
this.outputService.FindOne(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return objectCamelToSnake(findOneOutputResponse);
}
async update(updateOutPutDTO: IOutputUpdateRequest) {
console.log('OutputClientService', 'Update');
const updateOutputResponse = await new Promise((resolve, reject) => {
this.outputService.Update(objectSnakeToCamel(updateOutPutDTO)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return objectCamelToSnake(updateOutputResponse);
}
async remove(data: IIdRequest) {
console.log('OutputClientService', 'Remove');
const removeOutputResponse = await new Promise((resolve, reject) => {
this.outputService.Remove(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return removeOutputResponse;
}
}
-29
View File
@@ -1,29 +0,0 @@
export interface ICreateOutputRequest {
operation: string;
name: string;
plugin: string;
values: Values;
}
export interface Values {
prefix: string;
}
interface Info {
user_id: string;
customer_id: string;
}
export interface IIdRequest {
id: string;
info: Info;
}
export interface IOutputUpdateRequest {
id: string;
operation: string;
values: Values;
plugin: string;
name: string;
info: Info;
}
-25
View File
@@ -1,25 +0,0 @@
import { ClientOptions, Transport } from '@nestjs/microservices';
import {
PipelinePackages,
PipelineProtoFilePath,
} from '@victorradael/protospack';
import { credentials } from '@grpc/grpc-js';
export class PipelinesClientConfiguration {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.PIFACTORY_URL,
package: PipelinePackages,
credentials: credentials.createSsl(),
protoPath: PipelineProtoFilePath,
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
-235
View File
@@ -1,235 +0,0 @@
import { Inject, OnModuleInit } from '@nestjs/common';
import { ClientGrpc, Payload } from '@nestjs/microservices';
import {
PipelineServicesNames,
PipelinesServiceInterface,
} from '@victorradael/protospack';
import {
objectCamelToSnake,
objectSnakeToCamel,
} from 'src/utils/CaseConverter';
import {
ICreatePipelineDto,
IGetPipelineLogsRequest,
IIdRequest,
IUpdatePipelineRequest,
} from './interfaces';
export class PipelinesClientService implements OnModuleInit {
private pipelineService: PipelinesServiceInterface;
constructor(
@Inject('PIPELINES_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.pipelineService =
this.grpcClient.getService<PipelinesServiceInterface>(
PipelineServicesNames.PipelineService,
);
}
async create(@Payload() createPipelineDto: ICreatePipelineDto) {
console.log('PipelinesClientService', 'Create');
const createPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService
.Create(objectSnakeToCamel(createPipelineDto))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return createPipelineResponse;
}
async findAll(data: IIdRequest) {
console.log('PipelinesClientService', 'FindAll');
const findAllPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return findAllPipelineResponse;
}
async findOne(data: IIdRequest) {
console.log('PipelinesClientService', 'FindOne');
const findOnePipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.FindOne(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return findOnePipelineResponse;
}
async update(UpdatePipelineRequest: IUpdatePipelineRequest) {
console.log('PipelinesClientService', 'Update');
const updatePipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService
.Update(objectSnakeToCamel(UpdatePipelineRequest))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return updatePipelineResponse;
}
async remove(data: IIdRequest) {
console.log('PipelinesClientService', 'Remove');
const removePipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.remove(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return removePipelineResponse;
}
async getPipelineLogsMessages(data: IGetPipelineLogsRequest) {
console.log('PipelinesClientService', 'GetPipelineLogsMessages');
const logsPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.getPipelineLogs(data).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return logsPipelineResponse;
}
async getPipelineStatus(data: IIdRequest) {
console.log('PipelinesClientService', 'GetPipelineStatus');
const statusPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.getPipelineStatus(data).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return statusPipelineResponse;
}
async runPipeline({ id, info }: IIdRequest) {
console.log('PipelinesClientService', 'RunPipeline');
const statusPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService
.triggerPipeline(objectSnakeToCamel({ id, info }))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return statusPipelineResponse;
}
}
@@ -1,25 +0,0 @@
import { ClientOptions, Transport } from '@nestjs/microservices';
import {
TransformationPackages,
TransformationProtoFilePath,
} from '@victorradael/protospack';
import { credentials } from '@grpc/grpc-js';
export class TransformationsClientConfiguration {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.TRFACTORY_URL,
package: TransformationPackages,
credentials: credentials.createSsl(),
protoPath: TransformationProtoFilePath,
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
@@ -1,173 +0,0 @@
import { Controller, Inject, OnModuleInit, Post } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { TransformationsServiceInterface } from '@victorradael/protospack';
import {
objectCamelToSnake,
objectSnakeToCamel,
} from 'src/utils/CaseConverter';
import { ICreateTransformationsRequest, IIdRequest } from './interfaces';
@Controller('transformation')
export class TransformationsClientService implements OnModuleInit {
private transformationService: TransformationsServiceInterface;
constructor(
@Inject('TRANSFORMATIONS_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.transformationService =
this.grpcClient.getService<TransformationsServiceInterface>(
'TransformationService',
);
}
@Post()
async create(createTransformationsDto: ICreateTransformationsRequest) {
console.log('TransformationClientService', 'Create');
const createTransformationResponse = await new Promise(
(resolve, reject) => {
this.transformationService
.Create(objectSnakeToCamel(createTransformationsDto))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
},
)
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const convertedTransforms = createTransformationResponse[
'transformations'
].map((tr) => {
return objectCamelToSnake(tr);
});
return { transformations: convertedTransforms };
}
async findAll(data) {
console.log('TransformationClientService', 'FindAll');
const findAllTransformationResponse = await new Promise(
(resolve, reject) => {
this.transformationService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
},
)
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const convertedTransforms = findAllTransformationResponse[
'transformations'
].map((tr) => {
return objectCamelToSnake(tr);
});
return { transformations: convertedTransforms };
}
async findOne(data: IIdRequest) {
console.log('TransformationClientService', 'FindOne');
const findOneTransformationResponse = await new Promise(
(resolve, reject) => {
this.transformationService.FindOne(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
},
)
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return objectCamelToSnake(findOneTransformationResponse);
}
async update(updateTransformationDTO) {
console.log('TransformationClientService', 'Update');
const updateTransformationResponse = await new Promise(
(resolve, reject) => {
this.transformationService
.Update(objectSnakeToCamel(updateTransformationDTO))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
},
)
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return objectCamelToSnake(updateTransformationResponse);
}
async remove(data: IIdRequest) {
console.log('TransformationClientService', 'Remove');
const removeTransformationResponse = await new Promise(
(resolve, reject) => {
this.transformationService.Remove(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
},
)
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return objectCamelToSnake(removeTransformationResponse);
}
}
@@ -0,0 +1,38 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import ErrorBuilder from '../utils/ErrorBuilder';
/**
* ExceptionFilter to handle RpcException thrown by our microservices
*
* If it detects that the exception is already an **HttpException** or it is generated by our **ErrorBuilder**,
* it will return the exception without any treatment.
*/
@Catch(Error)
export class GrpcToHttpExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof HttpException) {
return response
.status(exception.getStatus())
.json(exception.getResponse());
}
const err =
exception.constructor.name === ErrorBuilder.name
? exception
: new ErrorBuilder(exception.details);
const { statusCode } = err.response;
return response.status(statusCode).json(err.response);
}
}
-7
View File
@@ -1,7 +0,0 @@
import { HttpExceptionFilter } from './http-exception.filter';
describe('HttpExceptionFilter', () => {
it('should be defined', () => {
expect(new HttpExceptionFilter()).toBeDefined();
});
});
+22 -6
View File
@@ -1,12 +1,20 @@
if (process.env.ENV !== 'local') require('elastic-apm-node/start');
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { writeFileSync } from 'fs';
import helmet from 'helmet';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AppModule } from './app.module';
import { writeFileSync } from 'fs';
async function bootstrap() {
DadosferaLogger.setupLogger({
serviceName: 'maestro',
serviceEnvironment: process.env.ENV,
});
const logger = new DadosferaLogger();
const app = await NestFactory.create(AppModule, {
logger,
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
@@ -17,16 +25,24 @@ async function bootstrap() {
app.use(helmet());
const date = new Date();
const config = new DocumentBuilder()
.setTitle('Maestro Grpc Documentation')
.setTitle('Maestro')
.setDescription('Documentation for Maestro gateway')
.setVersion('1.0')
.setVersion(
`${date.getDate()}.${
date.getMonth() + 1
}.${date.getFullYear()} - ${date.getHours()}:${date.getMinutes()}`,
)
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
writeFileSync('./swagger.json', JSON.stringify(document));
SwaggerModule.setup('api', app, document);
if (process.env.ENV === 'local') SwaggerModule.setup('api', app, document);
await writeFileSync('swagger.txt', JSON.stringify(JSON.stringify(document)));
await app.listen(3333);
}
-127
View File
@@ -1,127 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import axios from 'axios';
import {
ForbiddenException,
InternalServerErrorException,
NestMiddleware,
UnauthorizedException,
UseFilters,
} from '@nestjs/common';
import jwkToPem from 'jwk-to-pem';
import { decode, verify } from 'jsonwebtoken';
import { HttpExceptionFilter } from 'src/error/http-exception.filter';
@UseFilters(new HttpExceptionFilter())
export class LoggerMiddleware implements NestMiddleware {
use = async (request: Request, response: Response, next: NextFunction) => {
const idToken = request.get('Dadosfera-User');
const accessToken = request.get('Authorization');
const privateKey = process.env.JWT_PRIVATE_KEY;
const requiredMethod = request.method.trim();
const requiredRoute = request.route.path.split('/')[1].trim();
verify(idToken, privateKey, (err) => {
if (err) {
throw new UnauthorizedException();
}
});
const jwtDecoded: any = decode(idToken);
const permissions = jwtDecoded.user.permissions;
const clienId = jwtDecoded.user.customerId;
const customer = jwtDecoded.user.customer;
const userId = jwtDecoded.user.id;
await verifyToken(accessToken);
let havePermission = false;
permissions.forEach((permission) => {
permission = permission.split('/');
const method = permission[0].trim();
const route = permission[1].trim();
if (method === requiredMethod && route === requiredRoute) {
havePermission = true;
}
});
if (havePermission) {
request.body.info = {
customer_id: clienId,
user_id: userId,
customer,
};
next();
} else {
throw new ForbiddenException();
}
};
}
let pems: { [key: string]: Record<string, unknown> }[];
const setUp = async (region: string, id: string) => {
const URL = `https://cognito-idp.${region}.amazonaws.com/${id}/.well-known/jwks.json`;
try {
const response = await axios.get(URL);
if (response.status !== 200) {
throw new InternalServerErrorException();
}
const data = await response.data;
const { keys } = data;
pems = keys.map((key: any) => {
const modulus = key.n;
const exponent = key.e;
const keyType = key.kty;
const jwk = { kty: keyType, n: modulus, e: exponent };
const pem = jwkToPem(jwk);
const keyId = key.kid;
return { [keyId]: pem };
});
} catch (error) {
// console.log(error);
// console.log('Error! Unable to download JWKs');
}
};
const verifyToken = async (accessToken: string) => {
const awsRegion = process.env.AWS_REGION;
const awsPoolId = process.env.AWS_IDENTITY_POOL_ID;
try {
await setUp(awsRegion, awsPoolId);
if (!accessToken) {
throw new UnauthorizedException();
}
const user: any = decode(accessToken, { complete: true });
if (user === null) {
throw new UnauthorizedException();
}
const { kid } = user.header;
const pem = pems.filter((item: any) => item[kid]);
const pemValue: any = pem[0][kid];
if (!pem) {
throw new UnauthorizedException();
}
verify(accessToken, pemValue, (err: any) => {
if (err) {
throw new UnauthorizedException();
}
return;
});
} catch (error) {
throw new UnauthorizedException();
}
};
+171 -18
View File
@@ -1,27 +1,180 @@
import { Body, Controller, Post, UnauthorizedException } from '@nestjs/common';
import {
Body,
Controller,
Headers,
Post,
HttpCode,
HttpStatus,
Inject,
UseFilters,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
AuthSignInRequest,
AuthRefreshAccessTokenRequest,
AuthChangePasswordRequest,
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
AuthConfirmResetPasswordRequest,
AuthEnableTotpMfaRequest,
AuthDisableTotpMfaRequest,
AuthVerifyTotpMfaRequest,
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { AuthClientService } from 'src/clients/auth/client.service';
interface ISignIn {
username: string;
password: string;
}
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import { RequireAllPermissions } from 'src/authentication/authentication.decorator';
import { AuthClientService } from './auth.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter';
import { RequestUser, User } from 'src/authentication/user.decorator';
@ApiTags('Auth')
@UseFilters(new GrpcToHttpExceptionFilter())
@Controller('auth')
export class AuthController {
constructor(private authClient: AuthClientService) {}
logger: DadosferaLogger;
@Post()
async signIn(@Body() { username, password }: ISignIn) {
console.log(`/auth`, 'SignIn');
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private authClient: AuthClientService,
) {
this.logger = dadosferaLogger.logger;
}
const tokens = await this.authClient
.signIn({ username, password })
.then((result) => result)
.catch((err) => {
throw new UnauthorizedException(err.message);
});
@Post('/sso/snowflake')
@RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN)
@HttpCode(HttpStatus.OK)
async snowflakeSignIn(
@User() user: RequestUser,
@Body('RelayState') relayState: string,
) {
this.logger.info('/auth - snowflakeSignIn');
return tokens;
return this.authClient.snowflakeSignIn({
userId: user.user_id,
relayState,
});
}
@Post('sign-in')
@HttpCode(HttpStatus.OK)
async signIn(@Body() { username, password, totp }: AuthSignInRequest) {
this.logger.info('/auth - SignIn');
return this.authClient.signIn({ username, password, totp });
}
@Post('refresh-access-token')
@HttpCode(HttpStatus.OK)
async refreshAccessToken(
@Body() { refreshToken }: AuthRefreshAccessTokenRequest,
) {
this.logger.info('/auth - RefreshAccessToken');
return this.authClient.refreshAccessToken({ refreshToken });
}
@Post('change-password')
@HttpCode(HttpStatus.OK)
async changePassword(
@Body() body: AuthChangePasswordRequest,
@Headers() headers,
) {
this.logger.info('/auth - change-password');
const { oldPassword, newPassword } = body;
const { authorization: accessToken } = headers;
return this.authClient.changePassword({
accessToken,
oldPassword,
newPassword,
});
}
@Post('reset-password')
@HttpCode(HttpStatus.OK)
async resetPassword(@Body() body: AuthResetPasswordRequest) {
this.logger.info('/auth - reset-password');
const { username } = body;
return this.authClient.resetPassword({ username });
}
@Post('verify-reset-password-code')
@HttpCode(HttpStatus.OK)
async verifyResetPasswordCode(
@Body() body: AuthVerifyResetPasswordCodeRequest,
) {
this.logger.info('/auth - verify-reset-password-code');
const { username, code } = body;
return this.authClient.verifyResetPasswordCode({ username, code });
}
@Post('confirm-reset-password')
@HttpCode(HttpStatus.OK)
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
this.logger.info('/auth - confirm-reset-password');
const { username, code, newPassword } = body;
return this.authClient.confirmResetPassword({
username,
code,
newPassword,
});
}
@Post('enable-totp')
@HttpCode(HttpStatus.OK)
async enableTotpMFA(
@Body() body: AuthEnableTotpMfaRequest,
@Headers() headers,
) {
this.logger.info('/auth - enable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.enableTotpMFA({ accessToken, password });
}
@Post('disable-totp')
@HttpCode(HttpStatus.OK)
async disableTotpMFA(
@Body() body: AuthDisableTotpMfaRequest,
@Headers() headers,
) {
this.logger.info('/auth - disable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.disableTotpMFA({ accessToken, password });
}
@Post('dismiss-totp')
@HttpCode(HttpStatus.OK)
async dismissTotpMFA(@Headers() headers) {
this.logger.info('/auth - disable-totp');
const { authorization: accessToken } = headers;
return this.authClient.dismissTotpMFA({ accessToken });
}
@Post('verify-totp')
@HttpCode(HttpStatus.OK)
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
this.logger.info('/auth - enable-totp');
const { totp } = body;
const { authorization: accessToken } = headers;
return this.authClient.verifyTotp({ accessToken, totp });
}
}
+17
View File
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AuthController } from './auth.controller';
import { AuthClientService } from './auth.service';
import { DucClient } from '../duc/client.config';
const client = new DucClient();
@Module({
imports: [ClientsModule.register([client.providerOptions])],
controllers: [AuthController],
providers: [AuthClientService, DadosferaLogger],
exports: [AuthClientService],
})
export class AuthModule {}
-5
View File
@@ -1,5 +0,0 @@
describe('PipelinesGrpcServerService', () => {
it('should be defined', () => {
expect(2 + 2).toBe(4);
});
});
+148
View File
@@ -0,0 +1,148 @@
import { OnModuleInit, Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { lastValueFrom } from 'rxjs';
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { AuthProtoService as AuthServiceInterface } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import {
AuthSnowflakeSignInRequest,
AuthSignInRequest,
AuthRefreshAccessTokenRequest,
AuthEnableTotpMfaRequest,
AuthDisableTotpMfaRequest,
AuthDismissTotpMfaRequest,
AuthVerifyTotpMfaRequest,
AuthChangePasswordRequest,
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
AuthConfirmResetPasswordRequest,
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { DucClient } from '../duc/client.config';
@Injectable()
export class AuthClientService implements OnModuleInit {
logger: DadosferaLogger;
private authService: AuthServiceInterface;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
) {
this.logger = dadosferaLogger.logger;
}
onModuleInit() {
this.authService = this.grpcClient.getService<AuthServiceInterface>(
ProtoServices.AuthProtoService,
);
}
async getPublicKeys() {
this.logger.info('GetPublicKeys');
return lastValueFrom(this.authService.AuthGetPublicKeys({}));
}
async snowflakeSignIn(input: AuthSnowflakeSignInRequest) {
this.logger.info('snowflakeSignIn');
return lastValueFrom(this.authService.AuthSnowflakeSignIn(input));
}
async signIn({ username, password, totp }: AuthSignInRequest) {
this.logger.info('SignIn');
return lastValueFrom(
this.authService.AuthSignIn({ username, password, totp }),
);
}
async refreshAccessToken({ refreshToken }: AuthRefreshAccessTokenRequest) {
this.logger.info('RefreshAccessToken');
return lastValueFrom(
this.authService.AuthRefreshAccessToken({ refreshToken }),
);
}
async changePassword({
accessToken,
oldPassword,
newPassword,
}: AuthChangePasswordRequest) {
this.logger.info('ChangePassword');
return lastValueFrom(
this.authService.AuthChangePassword({
accessToken,
oldPassword,
newPassword,
}),
);
}
async resetPassword({ username }: AuthResetPasswordRequest) {
this.logger.info('resetPassword');
return lastValueFrom(this.authService.AuthResetPassword({ username }));
}
async verifyResetPasswordCode({
username,
code,
}: AuthVerifyResetPasswordCodeRequest) {
this.logger.info('verifyResetPasswordCode');
return lastValueFrom(
this.authService.AuthVerifyResetPasswordCode({ username, code }),
);
}
async confirmResetPassword({
username,
code,
newPassword,
}: AuthConfirmResetPasswordRequest) {
this.logger.info('confirmResetPassword');
return lastValueFrom(
this.authService.AuthConfirmResetPassword({
username,
code,
newPassword,
}),
);
}
async enableTotpMFA({ accessToken, password }: AuthEnableTotpMfaRequest) {
this.logger.info('enableTotpMFA');
return lastValueFrom(
this.authService.AuthEnableTotpMfa({ accessToken, password }),
);
}
async disableTotpMFA({ accessToken, password }: AuthDisableTotpMfaRequest) {
this.logger.info('disableTotpMFA');
return lastValueFrom(
this.authService.AuthDisableTotpMfa({ accessToken, password }),
);
}
async dismissTotpMFA({ accessToken }: AuthDismissTotpMfaRequest) {
this.logger.info('dismissTotpMFA');
return lastValueFrom(this.authService.AuthDismissTotpMfa({ accessToken }));
}
async verifyTotp({ accessToken, totp }: AuthVerifyTotpMfaRequest) {
this.logger.info('disableTotpMFA');
return lastValueFrom(
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
);
}
}
+12
View File
@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class LoginDto {
@ApiProperty()
username: string;
@ApiProperty()
password: string;
@ApiProperty({ required: false })
totp: string;
}
+241 -108
View File
@@ -1,50 +1,96 @@
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
Post,
Query,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
import { CatalogService } from './catalog.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
@ApiTags('Catalog')
@Controller('catalog')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
default:
action = req.method;
}
return user.permissions.includes(
PERMISSIONS_GROUPS.CATALOG.permissions[action].seqid,
);
})
export class CatalogController {
constructor(private catalogService: CatalogService) {}
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private catalogService: CatalogService,
) {
this.logger = dadosferaLogger.logger;
}
@Get('all')
async catalogAll(@Body() body) {
console.log(`/catalog`, 'ON CATALOG ALL ROUTE');
this.logger.info(`/catalog - ON CATALOG ALL ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.catalogAll(body);
const res = await this.catalogService.catalogAll(body);
return res;
}
@Get('data_apps')
async dataAppsAll(@Body() body) {
console.log(`/catalog`, 'ON FIND ALL DATA APPS ROUTE');
this.logger.info(`/catalog - ON FIND ALL DATA APPS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.dataAppsAll(body);
const res = await this.catalogService.dataAppsAll(body);
return res;
}
@Delete('data_apps/:id')
async dataAppsOne(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON FIND ONE DATA APP ROUTE');
this.logger.info(`/catalog - ON FIND ONE DATA APP ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.dataAppsOne(body, id);
const res = await this.catalogService.dataAppsOne(id, body);
return res;
}
@Get('dashboard-metabase')
async getAllDashboardMetabase(@Body() body) {
console.log(`/catalog`, 'ON GET ALL DASHBOARDS METABASE ROUTE');
this.logger.info(`/catalog - ON GET ALL DASHBOARDS METABASE ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getAllDashboardMetabase(body);
const res = await this.catalogService.getAllDashboardMetabase(body);
return res;
}
@@ -52,237 +98,324 @@ export class CatalogController {
@Get('dashboard-metabase/:id')
async getOneDashboardMetabase(@Body() body, @Param() params) {
const { id } = params;
console.log(`/catalog`, 'ON GET ONE DASHBOARD METABASE ROUTE');
this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getOneDashboardMetabase(body, id);
const res = await this.catalogService.getOneDashboardMetabase(id, body);
return res;
}
@Get('table-metadata')
async getAllTableMetadata(@Body() body) {
console.log(`/catalog`, 'ON GET ALL TABLES METADATA ROUTE');
async getAllTableMetadata(@Body() body, @Query() query) {
this.logger.info(`/catalog - ON GET ALL TABLES METADATA ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getAllTableMetadata(body);
return res;
if (!query) {
const res = await this.catalogService.getAllTableMetadata(body);
return res;
} else {
const res = await this.catalogService.getOneTableMetadata(body, query);
return res;
}
}
@Delete('table-metadata')
@Delete('table-metadata/:id')
async deleteOneTableMetadata(@Param() params, @Body() body) {
const { id } = params;
console.log(`/catalog`, 'ON DELETE ONE TABLE METADATA ROUTE');
this.logger.info(`/catalog - ON DELETE ONE TABLE METADATA ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.deleteOneTableMetadata(body, id);
const res = await this.catalogService.deleteOneTableMetadata(id, body);
return res;
}
@Get('column-metadata')
async getOneColumnMetadata(@Body() body) {
console.log(`/catalog`, 'ON GET ONE COLUMN METADATA ROUTE');
async getOneColumnMetadata(@Body() body, @Query() params) {
this.logger.info(`/catalog - ON GET ONE COLUMN METADATA ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getOneColumnMetadata(body);
const res = await this.catalogService.getOneColumnMetadata(body, params);
return res;
}
@Delete('column-metadata')
@Delete('column-metadata/:id')
async deleteOneColumnMetadata(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON DELETE ONE COLUMN METADATA ROUTE');
this.logger.info(`/catalog - ON DELETE ONE COLUMN METADATA ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteOneColumnMetadata(body, id);
const res = await this.catalogService.deleteOneColumnMetadata(id, body);
return res;
}
@Get('data-preview')
async getOneDataPreview(@Body() body) {
console.log(`/catalog`, 'ON GET ONE DATAPREVIEW ROUTE');
async getOneDataPreview(@Body() body, @Query() params) {
this.logger.info(`/catalog - ON GET ONE DATAPREVIEW ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getOneDataPreview(body);
const res = await this.catalogService.getOneDataPreview(body, params);
return res;
}
@Get('data-status')
async getDataStatus(@Body() body) {
console.log(`/catalog`, 'ON GET DATA STATUS ROUTE');
this.logger.info(`/catalog - ON GET DATA STATUS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await this.catalogService.getDataStatus(body);
const res = await catalogService.getDataStatus(body);
return res;
}
@Get('data-status')
async createDataStatus(@Body() body) {
this.logger.info(`/catalog - ON CREATE DATA STATUS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const res = await this.catalogService.createDataStatus(body);
return res;
}
@Get('data-description')
async getDataDescription(@Body() body) {
console.log(`/catalog`, 'ON GET DATA DESCRIPTION ROUTE');
this.logger.info(`/catalog - ON GET DATA DESCRIPTION ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await this.catalogService.getDataDescription(body);
const res = await catalogService.getDataDescription(body);
return res;
}
@Post('data-description')
async createDataDescription(@Body() body) {
this.logger.info(`/catalog - ON CREATE DATA DESCRIPTION ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const res = await this.catalogService.createDataDescription(body);
return res;
}
@Get('data-docs')
async getDataDocs(@Body() body) {
console.log(`/catalog`, 'ON GET DATA DOCS ROUTE');
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await this.catalogService.getDataDocs(body);
const res = await catalogService.getDataDocs(body);
return res;
}
@Post('data-docs')
async createDataDocs(@Body() body) {
this.logger.info(`/catalog - ON CREATE DATA DOCS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const res = await this.catalogService.createDataDocs(body);
return res;
}
@Get('data-rating')
async getDataRating(@Body() body) {
console.log(`/catalog`, 'ON GET DATA RATING ROUTE');
this.logger.info(`/catalog - ON GET DATA RATING ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await this.catalogService.getDataRating(body);
const res = await catalogService.getDataRating(body);
return res;
}
@Post('data-rating')
async createDataRating(@Body() body) {
this.logger.info(`/catalog - ON GET DATA RATING ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const res = await this.catalogService.createDataRating(body);
return res;
}
@Get('summary-rating/:id')
async getSummaryRating(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON GET SUMMARY RATING ROUTE');
const { id } = params;
async getSummaryRating() {
this.logger.info(`/catalog - ON GET SUMMARY RATING ROUTE`);
//const { id } = params;
const catalogService = new CatalogService();
//
const res = await catalogService.getSummaryRating(id, body);
//const res = await this.catalogService.getSummaryRating(id, body);
return res;
return {
avg_rating: 0,
rating_count: 0,
};
}
@Get('data-comment')
async getDataComment(@Body() body) {
console.log(`/catalog`, 'ON GET DATA COMMENT ROUTE');
this.logger.info(`/catalog - ON GET DATA COMMENT ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await this.catalogService.getDataComment(body);
const res = await catalogService.getDataComment(body);
return res;
}
@Post('data-comment')
async createDataComment(@Body() body) {
this.logger.info(`/catalog - ON GET DATA COMMENT ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const res = await this.catalogService.createDataComment(body);
return res;
}
@Get('data-review')
async getDataReview(@Body() body) {
console.log(`/catalog`, 'ON GET DATA REVIEW ROUTE');
async getDataReview(@Body() body, @Query() params) {
this.logger.info(`/catalog - ON GET DATA REVIEW ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getDataReview(body);
const res = await this.catalogService.getDataReview(body, params);
return res;
}
@Post('tags')
async createTags(@Body() body) {
console.log(`/catalog`, 'ON CREATE TAG ROUTE');
this.logger.info(`/catalog - ON CREATE TAG ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.createTags(body);
const res = await this.catalogService.createTags(body);
return res;
}
@Get('tags')
async findAllTags(@Body() body) {
console.log(`/catalog`, 'ON FIND ALL TAGS ROUTE');
this.logger.info(`/catalog - ON FIND ALL TAGS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.findAllTags(body);
const res = await this.catalogService.findAllTags(body);
return res;
}
@Delete('tags/:id')
async deleteTags(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON DELETE TAG ROUTE');
this.logger.info(`/catalog - ON DELETE TAG ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTags(body, id);
const res = await this.catalogService.deleteTags(id, body);
return res;
}
@Post('table-tags')
async createTableTags(@Body() body) {
console.log(`/catalog`, 'ON CREATE TABLE TAG ROUTE');
this.logger.info(`/catalog - ON CREATE TABLE TAG ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.createTableTags(body);
const res = await this.catalogService.createTableTags(body);
return res;
}
@Get('table-tags')
async getAllTableTags(@Body() body) {
console.log(`/catalog`, 'ON GET ALL TABLE TAGS ROUTE');
this.logger.info(`/catalog - ON GET ALL TABLE TAGS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.findAllTableTags(body);
const res = await this.catalogService.findAllTableTags(body);
return res;
}
@Delete('table-tags')
@Delete('table-tags/:id')
async deleteTableTags(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON CREATE TABLE TAGS ROUTE');
this.logger.info(`/catalog - ON CREATE TABLE TAGS ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTableTags(body, id);
const res = await this.catalogService.deleteTableTags(id, body);
return res;
}
@Get('table-rules')
async getTableRules(@Body() body) {
console.log(`/catalog`, 'ON GET TABLE RULES ROUTE');
this.logger.info(`/catalog - ON GET TABLE RULES ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const catalogService = new CatalogService();
const res = await catalogService.getTableRules(body);
const res = await this.catalogService.getTableRules(body);
return res;
}
@Delete('table-rules')
@Delete('table-rules/:id')
async deleteTableRules(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON DELETE TABLE RULES ROUTE');
this.logger.info(`/catalog - ON DELETE TABLE RULES ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTableRules(body, id);
const res = await this.catalogService.deleteTableRules(id, body);
return res;
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { CatalogController } from './catalog.controller';
import { CatalogService } from './catalog.service';
@Module({
controllers: [CatalogController],
providers: [CatalogService, DadosferaLogger],
})
export class CatalogModule {}
+123 -172
View File
@@ -1,308 +1,259 @@
import axios from 'axios';
class CatalogService {
_getNimbusUrl(body) {
const customer = body.info.customer.toLowerCase();
if (process.env.ENV === 'prd') {
return `https://nimbus-${customer}.dadosfera.ai`;
}
return `https://nimbus-${customer}.${process.env.ENV}.dadosfera.ai`;
}
async catalogAll(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/all/`,
);
console.log(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/all/`,
);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/all/`);
return data;
}
async dataAppsAll(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data_apps/`,
);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data_apps/`);
return data;
}
async dataAppsOne(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data_apps/${id}/`,
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/data_apps/${id}/`,
);
return data;
}
async getAllDashboardMetabase(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/dashboard-metabase/`,
`${nimbusUrl}/api/catalog/dashboard-metabase/`,
);
return data;
}
async getOneDashboardMetabase(body, id) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
async getOneDashboardMetabase(id, body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/dashboard-metabase/${id}`,
`${nimbusUrl}/api/catalog/dashboard-metabase/${id}`,
);
// const [, path] = data.iframe_url.split('.dadosfera');
// const host = `httpss://metabase-${body.info.customer.toLowerCase()}.dadosfera.ai`;
// data.iframe_url = host + path;
return data;
}
async getAllTableMetadata(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-metadata/`,
`${nimbusUrl}/api/catalog/table-metadata/`,
);
return data;
}
async getOneTableMetadata(body, params) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/table-metadata/`,
{ params: params },
);
return data;
}
async deleteOneTableMetadata(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-metadata/${id}`,
`${nimbusUrl}/api/catalog/table-metadata/${id}`,
);
return data;
}
async getOneColumnMetadata(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
async getOneColumnMetadata(body, params) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/column-metadata/`,
`${nimbusUrl}/api/catalog/column-metadata/`,
{ params: params },
);
return data;
}
async deleteOneColumnMetadata(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/column-metadata/${id}/`,
`${nimbusUrl}/api/catalog/column-metadata/${id}/`,
);
return data;
}
async getOneDataPreview(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-preview/`,
);
async getOneDataPreview(body, params) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-preview/`, {
params: params,
});
return data;
}
async getDataStatus(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-status/`);
return data;
}
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-status/`,
async createDataStatus(body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-status/`,
body,
);
return data;
}
async getDataDescription(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-description/`,
`${nimbusUrl}/api/catalog/data-description/`,
);
return data;
}
async createDataDescription(body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-description/`,
body,
);
return data;
}
async getDataDocs(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-docs/`);
return data;
}
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-docs/`,
async createDataDocs(body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-docs/`,
body,
);
return data;
}
async getDataRating(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-rating/`);
return data;
}
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-rating/`,
async createDataRating(body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-rating/`,
body,
);
return data;
}
async getSummaryRating(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/summary-rating/${id}`,
`${nimbusUrl}/api/catalog/summary-rating/${id}`,
);
return data;
}
async getDataComment(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-comment/`);
return data;
}
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-comment/`,
async createDataComment(body) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-comment/`,
body,
);
return data;
}
async getDataReview(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/data-review/`,
);
async getDataReview(body, params) {
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-review/`, {
params: params,
});
return data;
}
async createTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.post(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/tags/`,
body,
);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(`${nimbusUrl}/api/catalog/tags/`, body);
return data;
}
async findAllTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/tags/`,
body,
);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/tags/`, body);
return data;
}
async findAllTableTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-tags/`,
`${nimbusUrl}/api/catalog/table-tags/`,
body,
);
return data;
}
async deleteTags(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/tags/${id}`,
);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(`${nimbusUrl}/api/catalog/tags/${id}`);
return data;
}
async createTableTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-tags/`,
`${nimbusUrl}/api/catalog/table-tags/`,
body,
);
return data;
}
async deleteTableTags(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-tags/${id}`,
`${nimbusUrl}/api/catalog/table-tags/${id}`,
);
return data;
}
async getTableRules(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-rules/`,
`${nimbusUrl}/api/catalog/table-rules/`,
body,
);
return data;
}
async deleteTableRules(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera/api/catalog/table-rules/${id}`,
`${nimbusUrl}/api/catalog/table-rules/${id}`,
);
return data;
}
@@ -0,0 +1,30 @@
import { credentials } from '@grpc/grpc-js';
import {
ClientProviderOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
import { ConnectionTest } from '@dadosfera/protospack-v2';
export class ConnectionTestClientConfiguration {
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [ConnectionTest.ProtoPackages.ReadPackage],
credentials:
process.env.ENV === 'local' ? undefined : credentials.createSsl(),
protoPath: [ConnectionTest.ProtoPaths.ReadFilePath],
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
},
},
};
providerOptions: ClientProviderOptions = {
name: 'ConnectionTestGrpcClient',
...this.config,
};
}
@@ -0,0 +1,122 @@
import {
Body,
Controller,
HttpCode,
HttpStatus,
Inject,
Post,
UseFilters,
ValidationPipe,
} from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { User, RequestUser } from 'src/authentication/user.decorator';
import { ConnectionTestService } from './connection-test.service';
import {
ConnectionTestPingRes,
ConnectionTestPingReq,
ConnectionTestCredentialsReq,
ConnectionTestCredentialsRes,
ConnectionTestListSchemasReq,
ConnectionTestListSchemasRes,
ConnectionTestListTablesReq,
ConnectionTestListTablesRes,
GetTableMetadataRes,
GetTableMetadataReq,
} from './dto/connection-test';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { Authenticated } from 'src/authentication/authentication.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
@ApiTags('Connection Test')
@Controller('connection-test')
@UseFilters(new GrpcToHttpExceptionFilter())
@Authenticated()
export class ConnectionTestController {
logger: any;
constructor(
private connectionTestService: ConnectionTestService,
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
@Post('ping')
@ApiOkResponse({ type: ConnectionTestPingRes })
@HttpCode(HttpStatus.OK)
async connectionTestPing(
@User() user: RequestUser,
@Body(new ValidationPipe()) body: ConnectionTestPingReq,
) {
this.logger.info('/connection-test/ping', {
user: user.user_id,
customer: user.customer_name,
});
return this.connectionTestService.connectionTestPing(body, user);
}
@Post('credentials')
@ApiOkResponse({ type: ConnectionTestCredentialsRes })
@HttpCode(HttpStatus.OK)
async connectionTestCredentials(
@User() user: RequestUser,
@Body() body: ConnectionTestCredentialsReq,
) {
this.logger.info('/connection-test/credentials', {
user: user.user_id,
customer: user.customer_name,
});
return this.connectionTestService.connectionTestCredentials(body, user);
}
@Post('schemas')
@ApiOkResponse({ type: ConnectionTestListSchemasRes })
@HttpCode(HttpStatus.OK)
async connectionTestListSchemas(
@User() user: RequestUser,
@Body(new ValidationPipe()) body: ConnectionTestListSchemasReq,
) {
this.logger.info('/connection-test/schemas', {
user: user.user_id,
customer: user.customer_name,
});
return this.connectionTestService.connectionTestListSchemas(
body,
user.customer_name,
);
}
@Post('tables')
@ApiOkResponse({ type: ConnectionTestListTablesRes })
@HttpCode(HttpStatus.OK)
async connectionTestListTables(
@User() user: RequestUser,
@Body(new ValidationPipe()) body: ConnectionTestListTablesReq,
) {
this.logger.info('/connection-test/tables', {
user: user.user_id,
customer: user.customer_name,
});
return this.connectionTestService.connectionTestListTables(
body,
user.customer_name,
);
}
@Post('table-metadata')
@ApiOkResponse({ type: GetTableMetadataRes })
@HttpCode(HttpStatus.OK)
async getTableMetadata(
@User() user: RequestUser,
@Body(new ValidationPipe()) body: GetTableMetadataReq,
) {
this.logger.info('/connection-test/table-metadata', {
user: user.user_id,
customer: user.customer_name,
});
return this.connectionTestService.getTableMetadata(
body,
user.customer_name,
);
}
}
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { ConnectionTestController } from './connection-test.controller';
import { ConnectionTestService } from './connection-test.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { ClientsModule } from '@nestjs/microservices';
import { ConnectionTestClientConfiguration } from './connection-test-client.config';
import { ConnectionModule } from '../connection/connection.module';
const client = new ConnectionTestClientConfiguration();
@Module({
controllers: [ConnectionTestController],
providers: [ConnectionTestService, DadosferaLogger],
imports: [ClientsModule.register([client.providerOptions]), ConnectionModule],
})
export class ConnectionTestModule {}
@@ -0,0 +1,191 @@
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ConnectionTest } from '@dadosfera/protospack-v2';
import { lastValueFrom } from 'rxjs';
import {
ConnectionTestCredentialsReq,
ConnectionTestCredentialsRes,
ConnectionTestListSchemasReq,
ConnectionTestListSchemasRes,
ConnectionTestListTablesReq,
ConnectionTestListTablesRes,
ConnectionTestPingReq,
ConnectionTestPingRes,
GetTableMetadataReq,
GetTableMetadataRes,
} from './dto/connection-test';
import { ConnectionClientService } from '../connection/client.service';
import {
CreateConnectionDto,
DatabaseConnectionPropertiesDto,
} from '../connection/dtos/connection';
import { RequestUser } from 'src/authentication/user.decorator';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
@Injectable()
export class ConnectionTestService {
private connectionTestReadClient: ConnectionTest.ReadService.ConnectionTestReadServices;
constructor(
@Inject('ConnectionTestGrpcClient') private readonly grpcClient: ClientGrpc,
private connectionsService: ConnectionClientService,
) {
this.connectionTestReadClient =
grpcClient.getService<ConnectionTest.ReadService.ConnectionTestReadServices>(
ConnectionTest.ProtoServices.ConnectionTestReadServices,
);
}
async connectionTestPing(
body: ConnectionTestPingReq,
user: RequestUser,
): Promise<ConnectionTestPingRes> {
const {
host: endpoint,
plugin,
port,
network_config_id,
connection_id,
} = body;
const { user_id, customer_id, customer_name } = user;
const metadata = PackTheMetadata({
customer_name,
customer_id,
user_id,
});
if (connection_id) {
const con = await this.connectionsService.getConnectionDetails({
body,
metadata,
});
const conPayload = JSON.parse(con.connection.properties);
const { plugin, network_config_id } = con.connection;
const { host, port, endpoint } = conPayload;
return lastValueFrom(
this.connectionTestReadClient.ConnectionTestPing({
customer_name,
endpoint: endpoint || host,
network_config_id,
plugin,
port: port ? port.toString() : undefined,
connection_id,
}),
);
} else {
return lastValueFrom(
this.connectionTestReadClient.ConnectionTestPing({
customer_name,
endpoint,
network_config_id,
plugin,
port: port ? port.toString() : undefined,
connection_id,
}),
);
}
}
async connectionTestCredentials(
body: ConnectionTestCredentialsReq,
user: RequestUser,
): Promise<ConnectionTestCredentialsRes> {
const { plugin, ...connectionBody } = body;
let { connection_id } = body;
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
if (body.network_config_id == '') {
body.network_config_id = undefined;
}
/********* CREATE CONNECTION *********/
if (!connection_id) {
const properties: DatabaseConnectionPropertiesDto = {
...connectionBody.properties,
plugin,
credentials_type: 'basic_auth',
};
const createConnectionBody: CreateConnectionDto = {
...body,
properties,
};
const { connection } = await this.connectionsService.createConnection(
createConnectionBody,
metadata,
);
connection_id = connection.id;
}
/************************************/
const { operation_result } = await lastValueFrom(
this.connectionTestReadClient.ConnectionTestCredentials({
connection_id,
customer_name,
plugin,
}),
).catch(async (e) => {
if (!body.connection_id) {
await this.connectionsService.deleteConnection({
body: { id: connection_id },
metadata,
});
}
throw e;
});
if (!operation_result && !body.connection_id) {
await this.connectionsService.deleteConnection({
body: { id: connection_id },
metadata,
});
connection_id = null;
}
return { operation_result, connection_id };
}
async connectionTestListSchemas(
body: ConnectionTestListSchemasReq,
customer_name: string,
): Promise<ConnectionTestListSchemasRes> {
const { connection_id, plugin } = body;
return lastValueFrom(
this.connectionTestReadClient.ListSchemas({
connection_id,
customer_name,
plugin,
}),
);
}
async connectionTestListTables(
body: ConnectionTestListTablesReq,
customer_name: string,
): Promise<ConnectionTestListTablesRes> {
const { connection_id, plugin, schema } = body;
return lastValueFrom(
this.connectionTestReadClient.ListTables({
connection_id,
customer_name,
plugin,
schema,
}),
);
}
async getTableMetadata(
body: GetTableMetadataReq,
customer_name: string,
): Promise<GetTableMetadataRes> {
const { schema, plugin, table_list, connection_id } = body;
return lastValueFrom(
this.connectionTestReadClient.GetTableMetadata({
connection_id,
customer_name,
plugin,
schema,
table_list,
}),
);
}
}
@@ -0,0 +1,133 @@
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { IsString, IsOptional } from 'class-validator';
import { DatabaseConnectionPropertiesDto } from 'src/modules/connection/dtos/connection';
import { CreateConnectionDto } from 'src/modules/connection/dtos/connection';
export class ColumnDto {
@ApiProperty()
name: string;
@ApiProperty()
type: string;
}
export class TableMetadataDto {
@ApiProperty()
table_name: string;
@ApiProperty({ type: [ColumnDto] })
columns: ColumnDto[];
@ApiProperty({ type: [ColumnDto] })
references: ColumnDto[];
}
export class ConnectionTestPingReq {
@ApiPropertyOptional()
@IsString()
@IsOptional()
plugin: string;
@ApiPropertyOptional()
@IsString()
@IsOptional()
host: string;
@ApiPropertyOptional()
@IsOptional()
port: number;
@ApiPropertyOptional()
@IsOptional()
@IsString()
network_config_id?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
connection_id: string;
}
export class ConnectionTestPingRes {
@ApiProperty()
operation_result: boolean;
}
export class ConnectionTestCredentialsProperties extends OmitType(
DatabaseConnectionPropertiesDto,
['plugin', 'credentials_type'],
) {}
export class ConnectionTestCredentialsReq extends OmitType(
CreateConnectionDto,
['properties'],
) {
@ApiProperty()
properties: ConnectionTestCredentialsProperties;
@ApiPropertyOptional()
connection_id?: string;
}
export class ConnectionTestCredentialsRes {
@ApiProperty()
operation_result: boolean;
@ApiProperty()
connection_id: string;
}
export class ConnectionTestListSchemasReq {
@ApiProperty()
@IsString()
plugin: string;
@ApiProperty()
@IsString()
connection_id: string;
}
export class ConnectionTestListSchemasRes {
@ApiProperty()
operation_result: boolean;
@ApiProperty()
schema_list: string[];
}
export class ConnectionTestListTablesReq {
@ApiProperty()
@IsString()
plugin: string;
@ApiProperty()
@IsString()
connection_id: string;
@ApiProperty()
@IsString()
schema: string;
}
export class ConnectionTestListTablesRes {
@ApiProperty()
operation_result: boolean;
@ApiProperty()
table_list: string[];
}
export class GetTableMetadataReq {
@ApiProperty()
@IsString()
connection_id: string;
@ApiProperty()
@IsString()
plugin: string;
@ApiProperty()
@IsString()
schema: string;
@ApiProperty()
@IsString({ each: true })
table_list: string[];
}
export class GetTableMetadataRes {
@ApiProperty()
operation_result: boolean;
@ApiProperty({ type: [TableMetadataDto] })
tables_metadata: TableMetadataDto[];
}
+40
View File
@@ -0,0 +1,40 @@
import { credentials } from '@grpc/grpc-js';
import {
ClientsProviderAsyncOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
import { ConnectionManager } from '@dadosfera/protospack-v2';
export class ConnectionClientConfiguration {
public name = 'ConnectionClientConfiguration';
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [
ConnectionManager.ProtoPackages.WritePackage,
ConnectionManager.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
protoPath: [
ConnectionManager.ProtoPaths.WriteFilePath,
ConnectionManager.ProtoPaths.ReadFilePath,
],
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
},
},
};
providerOptions: ClientsProviderAsyncOptions = {
name: this.name,
...this.config,
};
}
+370
View File
@@ -0,0 +1,370 @@
import {
OnModuleInit,
Inject,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ConnectionManager } from '@dadosfera/protospack-v2';
import { lastValueFrom } from 'rxjs';
import {
ConnectionApiConnection,
ConnectionRes,
ConnectionsRes,
ConnectionToCatalogDto,
CreateConnectionDto,
UpdateConnectionDto,
} from './dtos/connection';
import { ConnectionClientConfiguration } from './client.config';
import {
GetConnectionDetailsResponse,
INTERNAL_GetAllConnectionsResponse,
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/messages';
import { UsersService } from '../users/users.service';
import { ConnectorClientService } from '../connector/client.service';
import {
ConnectionsApiNetworkConfig,
NetworkConfigDto,
} from '../network-config/dto/network-config';
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
export class ConnectionClientService implements OnModuleInit {
private connectionServiceRead: ConnectionManager.ReadService.ConnectionManagerReadServices;
private connectionServiceWrite: ConnectionManager.WriteService.ConnectionManagerWriteServices;
logger: DadosferaLogger;
constructor(
@Inject(ConnectionClientConfiguration.name)
private readonly grpcClient: ClientGrpc,
private usersService: UsersService,
private connectorService: ConnectorClientService,
private dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
onModuleInit() {
this.connectionServiceWrite =
this.grpcClient.getService<ConnectionManager.WriteService.ConnectionManagerWriteServices>(
ConnectionManager.ProtoServices.ConnectionManagerWriteServices,
);
this.connectionServiceRead =
this.grpcClient.getService<ConnectionManager.ReadService.ConnectionManagerReadServices>(
ConnectionManager.ProtoServices.ConnectionManagerReadServices,
);
}
async createConnection(
body: CreateConnectionDto,
metadata,
): Promise<ConnectionRes> {
const {
connector_id,
description,
image_url,
name,
network_config_id,
properties,
type,
connector_name,
plugin,
} = body;
return lastValueFrom(
this.connectionServiceWrite.CreateConnection(
{
connector_id,
connector_name,
description,
image_url,
name,
network_config_id,
properties: JSON.stringify(properties),
type,
plugin,
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async updateConnection(
id: string,
body: UpdateConnectionDto,
metadata,
): Promise<ConnectionRes> {
const { description, name, network_config_id, properties } = body;
return lastValueFrom(
this.connectionServiceWrite.UpdateConnection(
{
id,
description,
name,
network_config_id,
properties: JSON.stringify(properties),
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async deleteConnection({ body, metadata }): Promise<ConnectionRes> {
const { id } = body;
return lastValueFrom(
this.connectionServiceWrite.DeleteConnection(
{
id,
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getAllConnections({ body, metadata }): Promise<ConnectionsRes> {
const { search, filters, size, page } = body;
return lastValueFrom(
this.connectionServiceRead.GetAllConnection(
{ search, filters: JSON.stringify(filters), size, page },
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getConnectionDetails({
body,
metadata,
}): Promise<GetConnectionDetailsResponse> {
const id = body.connection_id || body.id;
return lastValueFrom(
this.connectionServiceRead.GetConnectionDetails({ id }, metadata),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async INTERNAL_getAllConnections({
customer_name,
}: {
customer_name: string;
}): Promise<INTERNAL_GetAllConnectionsResponse> {
return lastValueFrom(
this.connectionServiceRead.INTERNAL_GetAllConnections({ customer_name }),
);
}
async catalogExistingConnection(data: {
connection?: ConnectionToCatalogDto;
networkConfig?: NetworkConfigDto;
}) {
return lastValueFrom(
this.connectionServiceRead.INTERNAL_CatalogExistingConnection({
data_str: JSON.stringify(data),
}),
);
}
async synchronizeConnections(apply: boolean) {
const connectors = await this.connectorService
.getAllConnectors({
search: undefined,
filters: undefined,
language: 'pt-br',
page: 1,
size: 500,
})
.then(({ connectors: connectorsStr }) =>
JSON.parse(connectorsStr).map((c) => c),
);
const { customers } = await this.usersService.findAllCustomers();
const customer_connections: {
customer_name: string;
customer_id: string;
productConnections: Record<string, any>[];
platformConnections: ConnectionApiConnection[];
productNetworkConfigs: Record<string, any>[];
platformNetworkConfigs: ConnectionsApiNetworkConfig[];
}[] = [];
for (const customer of customers) {
const { name: customer_name, id: customer_id } = customer;
const customer_connection = await this.INTERNAL_getAllConnections({
customer_name,
}).then((res) => ({
customer_name,
customer_id,
platformConnections: res.platformConnections
? JSON.parse(res.platformConnections)
: [],
productConnections: res.productConnections
? JSON.parse(res.productConnections)
: [],
productNetworkConfigs: res.productNetworkConfigs
? JSON.parse(res.productNetworkConfigs)
: [],
platformNetworkConfigs: res.platformNetworkConfigs
? JSON.parse(res.platformNetworkConfigs)
: [],
}));
customer_connections.push(customer_connection);
}
this.logger.info(
'DEBUG_ConnectionsMigrations - ' + JSON.stringify(customer_connections),
);
const migrations: {
customer_name: string;
status: any[];
productConnectionsMissing: ConnectionApiConnection[];
productNetworkConfigsMissing: ConnectionsApiNetworkConfig[];
}[] = [];
for (const customer_connection of customer_connections) {
const productConnectionsMissing: ConnectionApiConnection[] = [];
const productNetworkConfigsMissing: ConnectionsApiNetworkConfig[] = [];
const status: any[] = [];
let updatePromises: Promise<any>[] = [];
this.logger.info(
'DEBUG_ConnectionsMigrations - customer_connections ' +
JSON.stringify(customer_connection),
);
const {
customer_name,
productConnections,
platformConnections,
productNetworkConfigs,
platformNetworkConfigs,
customer_id,
} = customer_connection;
platformConnections?.forEach((plc) => {
const productConnection = productConnections.find(
(prc) => prc.id === plc.config_id,
);
if (!productConnection) productConnectionsMissing.push(plc);
});
platformNetworkConfigs?.forEach((platNetConfig) => {
const productNetworkConfig = productNetworkConfigs.find(
(prodNetConfig) => prodNetConfig.id === platNetConfig.config_id,
);
if (!productNetworkConfig)
productNetworkConfigsMissing.push(platNetConfig);
});
if (apply) {
for (const network_config of productNetworkConfigsMissing) {
const networkConfigToCatalog = {
id: network_config.config_id,
created_at: network_config.created_at,
updated_at: network_config.updated_at,
customer_name: customer_name,
customer_id: customer_id,
user_id: '',
network_type_id: '',
network_type_name: network_config.type,
type: network_config.type,
plugin: network_config.plugin,
name: network_config.name,
description: network_config.description,
properties: undefined,
keywords: undefined,
tags: undefined,
};
updatePromises.push(
this.catalogExistingConnection({
networkConfig: networkConfigToCatalog,
}),
);
}
this.logger.info(
'DEBUG_ConnectionsMigrations - Creating missing network configs for customer' +
customer_name +
' - ' +
JSON.stringify(customer_connection),
);
const updateStatuses = await Promise.all(updatePromises);
status.push(...updateStatuses);
updatePromises = [];
for (const connection of productConnectionsMissing) {
const connector = connectors.find(
(c) => c._source.plugin === connection.plugin,
);
const connectionToCatalog = {
id: connection.config_id,
created_at: connection.created_at,
updated_at: new Date().toISOString(),
customer_id: customer_id,
user_id: '',
plugin: connection.plugin,
name: connection.name,
description: connection.description,
image_url: connector._source.image,
network_config_id: connection.network_config_id,
type: connection.type,
connector_id: connector._id,
connector_name: connector._source.name,
in_use: 1,
username: '',
customer_name,
};
updatePromises.push(
this.catalogExistingConnection({ connection: connectionToCatalog }),
);
}
this.logger.info(
'DEBUG_ConnectionsMigrations - Creating missing connections for customer' +
' - ' +
customer_name +
JSON.stringify(customer_connection),
);
const updateStatuses_2 = await Promise.all(updatePromises);
status.push(...updateStatuses_2);
}
this.logger.info(
'DEBUG_ConnectionsMigrations - migrations for customer ' +
customer_name +
' - ' +
JSON.stringify(customer_connection),
);
migrations.push({
customer_name,
status,
productConnectionsMissing,
productNetworkConfigsMissing,
});
}
return { migrations };
}
}
@@ -0,0 +1,202 @@
import {
Body,
Controller,
Post,
Inject,
Put,
Param,
Delete,
Get,
Headers,
Query,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ConnectionClientService } from './client.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import {
Authenticated,
RequireAllPermissions,
} from 'src/authentication/authentication.decorator';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import { RequestUser, User } from 'src/authentication/user.decorator';
import { ValidationPipe } from '../../pipes/object-validation.pipe';
import {
ConnectionDetailsRes,
ConnectionRes,
ConnectionsRes,
UpdateConnectionDto,
} from './dtos/connection';
import { CreateConnectionDto } from './dtos/connection';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
const connectionPermissions = PERMISSIONS_GROUPS.CONNECTION.permissions;
@ApiTags('connections')
@ApiBearerAuth()
@Authenticated()
@Controller('connections')
export class ConnectionController {
logger: any;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private clientService: ConnectionClientService,
) {
this.logger = dadosferaLogger.logger;
}
@Get('connections-migration')
@RequireAllPermissions(PERMISSIONS_GROUPS.DADOSFERA.permissions.ADMIN)
async synchronizeConnections(@Query('apply') apply: string) {
return this.clientService.synchronizeConnections(apply === 'true');
}
@Post()
@RequireAllPermissions(connectionPermissions.CREATE)
async createConnection(
@User() user: RequestUser,
@Body(new ValidationPipe()) body: CreateConnectionDto,
): Promise<ConnectionRes> {
this.logger.info('/connections - Create Connection');
const { user_id, customer_id, customer_name, username } = user;
const metadata = PackTheMetadata({
user_id,
customer_id,
customer_name,
username,
});
const response: any = await this.clientService.createConnection(
body,
metadata,
);
return response;
}
@Put('/:id')
@RequireAllPermissions(connectionPermissions.CREATE)
async updateConnection(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language: string,
@Param('id') id: string,
@Body(new ValidationPipe()) body: UpdateConnectionDto,
): Promise<ConnectionRes> {
this.logger.info('/connections - Update Connection');
if (!language) language = 'en-us';
const { user_id, customer_id, customer_name, username } = user;
const metadata = PackTheMetadata({
user_id,
customer_id,
customer_name,
language,
username,
});
const response: any = await this.clientService.updateConnection(
id,
body,
metadata,
);
return response;
}
@Delete('/:id')
@RequireAllPermissions(connectionPermissions.DELETE)
async deleteConnection(
@User() user: RequestUser,
@Param('id') id: string,
): Promise<ConnectionRes> {
this.logger.info('/connections - Delete Connection');
const { user_id, customer_id, customer_name, username } = user;
const metadata = PackTheMetadata({
user_id,
customer_id,
customer_name,
username,
});
const response: any = await this.clientService.deleteConnection({
body: { id },
metadata,
});
return response;
}
@Get()
async getAllConnections(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language,
@Query() queries,
): Promise<ConnectionsRes> {
this.logger.info('/connections - Get All Connection');
if (!language) language = 'en-us';
const { user_id, customer_id, customer_name, username } = user;
const { search, size, page, ...filters } = queries;
const metadata = PackTheMetadata({
user_id,
customer_id,
customer_name,
language,
username,
});
const response = await this.clientService.getAllConnections({
body: { search, size, page, filters },
metadata,
});
return response;
}
@Get('/:id')
async getConnectionDetails(
@Headers('Dadosfera-Lang') language,
@User() user: RequestUser,
@Param('id') id,
@Query('details') details,
): Promise<ConnectionDetailsRes> {
this.logger.info('/connections - Get Connection Details');
if (!language) language = 'en-us';
const { user_id, customer_id, customer_name, username } = user;
const metadata = PackTheMetadata({
user_id,
customer_id,
customer_name,
username,
details,
language,
});
const response: any = await this.clientService.getConnectionDetails({
body: { id },
metadata,
});
if (details === 'true') {
const properties = JSON.parse(response.connection.properties);
Object.assign(response.connection, {
pipelines: JSON.parse(response.connection.pipelines),
connection_controls: properties.connection_controls,
config_controls: properties.config_controls,
connection_steps: properties.connection_steps,
});
delete response.connection.properties;
} else {
Object.assign(response.connection, {
properties: JSON.parse(response.connection.properties),
pipelines: JSON.parse(response.connection.pipelines),
});
}
return response;
}
}
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { ConnectionClientService } from './client.service';
import { ConnectionClientConfiguration } from './client.config';
import { ConnectionController } from './connection.controller';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { UsersModule } from '../users/users.module';
import { ConnectorModule } from '../connector/connector.module';
const client = new ConnectionClientConfiguration();
@Module({
imports: [
ClientsModule.register([client.providerOptions]),
UsersModule,
ConnectorModule,
],
controllers: [ConnectionController],
providers: [ConnectionClientService, DadosferaLogger],
exports: [ConnectionClientService],
})
export class ConnectionModule {}
+189
View File
@@ -0,0 +1,189 @@
import {
ApiProperty,
ApiPropertyOptional,
OmitType,
PickType,
} from '@nestjs/swagger';
import {
Connection,
ConnectionToCatalog,
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities';
import {
GetAllConnectionResponse,
GetConnectionDetailsResponse,
GetConnectionResponse,
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/messages';
export type ConnectionTypes = 'database' | 'file' | 'application';
export type ConnectionCredentialsType =
| 'basic_auth'
| 'iam_user'
| 'role_name'
| 'oauth'
| 'api_key'
| 'service_account'
| 'headers_authF';
export interface ConnectionApiConnection {
config_id: string;
plugin: string;
secret_id: string;
created_at: string;
network_config_id: null;
type: ConnectionTypes;
credentials_type: ConnectionCredentialsType;
credentials: Record<string, any>;
sensitive_fields: string[];
name: string;
customer_name: string;
description: string;
updated_at: string;
}
export class ConnectionDto implements Connection {
// ---Automatically generated information will not be sent by the frontend--- //
@ApiProperty()
id: string;
@ApiProperty()
customer_id: string;
@ApiProperty()
customer_name: string;
@ApiProperty()
user_id: string;
@ApiProperty()
updated_at: string;
@ApiProperty()
created_at: string;
@ApiProperty()
pipelines: string;
// ---Information sent by the frontend--- //
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiProperty()
type: string;
@ApiPropertyOptional()
network_config_id: string | null;
@ApiProperty()
properties: DatabaseConnectionPropertiesDto | any;
// ---Information sent by the frontend (Heirs of Connector)--- //
@ApiProperty()
connector_id: string;
@ApiProperty()
connector_name: string;
@ApiProperty()
plugin: string;
@ApiProperty()
image_url: string;
}
export class ConnectionToCatalogDto implements ConnectionToCatalog {
// ---Automatically generated information - will not be sent by the frontend--- //
id: string;
customer_id: string;
updated_at: string;
created_at: string;
// ---Information extracted from token--- //
user_id: string;
username: string;
customer_name: string;
// ---Information sent by the frontend--- //
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiProperty()
type: string;
@ApiPropertyOptional()
network_config_id: string | null;
// ---Information sent by the frontend (Heirs of Connector)--- //
@ApiProperty()
connector_id: string;
@ApiProperty()
connector_name: string;
@ApiProperty()
plugin: string;
@ApiProperty()
image_url: string;
}
export class CreateConnectionDto extends OmitType(ConnectionDto, [
'id',
'customer_id',
'customer_name',
'user_id',
'created_at',
'updated_at',
'pipelines',
]) {}
export class UpdateConnectionDto extends PickType(ConnectionDto, [
'name',
'description',
'network_config_id',
'properties',
]) {}
export class ConnectionsRes implements GetAllConnectionResponse {
@ApiProperty({ type: [ConnectionToCatalogDto] })
connections: ConnectionToCatalogDto[];
@ApiProperty()
message: string;
}
export class ConnectionRes implements GetConnectionResponse {
@ApiProperty()
connection?: ConnectionToCatalogDto;
@ApiProperty()
message: string;
}
export class ConnectionDetailsRes implements GetConnectionDetailsResponse {
@ApiProperty()
connection?: ConnectionDto;
@ApiProperty()
message: string;
}
export class DatabaseConnectionPropertiesDto {
@ApiProperty()
credentials_type: 'basic_auth';
@ApiProperty()
plugin: string;
@ApiProperty()
host: string;
@ApiProperty()
port: string;
@ApiProperty()
username: string;
@ApiProperty()
password: string;
@ApiProperty()
database: string;
}
+39
View File
@@ -0,0 +1,39 @@
import { credentials } from '@grpc/grpc-js';
import {
ClientsProviderAsyncOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
import { ConnectorManager } from '@dadosfera/protospack-v2';
export class ConnectorClientConfiguration {
public name = 'ConnectorClientConfiguration';
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [
ConnectorManager.ProtoPackages.WritePackage,
ConnectorManager.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
protoPath: [
ConnectorManager.ProtoPaths.WriteFilePath,
ConnectorManager.ProtoPaths.ReadFilePath,
],
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
},
},
};
providerOptions: ClientsProviderAsyncOptions = {
name: this.name,
...this.config,
};
}
+195
View File
@@ -0,0 +1,195 @@
import { Metadata } from '@grpc/grpc-js';
import {
OnModuleInit,
Inject,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ConnectorManager } from '@dadosfera/protospack-v2';
import { lastValueFrom } from 'rxjs';
import { ConnectorClientConfiguration } from './client.config';
export class ConnectorClientService implements OnModuleInit {
private connectorServiceRead: ConnectorManager.ReadService.ConnectorManagerReadServices;
private connectorServiceWrite: ConnectorManager.WriteService.ConnectorManagerWriteServices;
constructor(
@Inject(ConnectorClientConfiguration.name)
private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.connectorServiceWrite =
this.grpcClient.getService<ConnectorManager.WriteService.ConnectorManagerWriteServices>(
ConnectorManager.ProtoServices.ConnectorManagerWriteServices,
);
this.connectorServiceRead =
this.grpcClient.getService<ConnectorManager.ReadService.ConnectorManagerReadServices>(
ConnectorManager.ProtoServices.ConnectorManagerReadServices,
);
}
async uploadConnector(uploadConnector) {
const connector: ConnectorManager.Entities.ConnectorCreateRequest = {
file: {
buffer: uploadConnector.file.buffer,
mimetypes: uploadConnector.file.mimetype,
},
connector: JSON.stringify(uploadConnector.connector),
};
const serviceBody: ConnectorManager.Messages.RegisterConnectorRequest = {
connector,
};
return lastValueFrom(
this.connectorServiceWrite.RegisterConnector(serviceBody),
).catch((err) => {
console.log(err.details);
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async uploadFile(uploadFile) {
const body: ConnectorManager.Messages.UploadFileRequest = {
file: {
buffer: uploadFile.file.buffer,
mimetypes: uploadFile.file.mimetype,
},
name: uploadFile.name,
};
return lastValueFrom(this.connectorServiceWrite.UploadFile(body)).catch(
(err) => {
console.log(err.details);
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
},
);
}
async uploadConnectorsFile(uploadFile) {
const connectors = JSON.parse(uploadFile.file.buffer.toString('utf8'));
const body = connectors.map((connector) => {
return JSON.stringify(connector);
});
const request = {
connectors: body,
};
return lastValueFrom(
this.connectorServiceWrite.RegisterMultipleConnectorsWithoutImage(
request,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getAllConnectors(body) {
const meta = new Metadata();
meta.add('Language', body.language);
return lastValueFrom(
this.connectorServiceRead.GetAllConnectors(
{
search: body.search,
filters: JSON.stringify(body.filters),
language: body.language,
page: body.page,
size: body.size,
},
meta,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getConnectorDetails(plugin: string, language: string) {
const meta = new Metadata();
meta.add('Language', language);
return lastValueFrom(
this.connectorServiceRead.GetConnectorDetails(
{
plugin,
},
meta,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getConnector(plugin: string, language: string) {
const meta = new Metadata();
meta.add('Language', language);
return lastValueFrom(
this.connectorServiceRead.GetConnector(
{
plugin,
},
meta,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async getConnectorsTags() {
return lastValueFrom(this.connectorServiceRead.GetConnectorsTags({})).catch(
(err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
},
);
}
async deleteConnector(plugin: string, version: string) {
return lastValueFrom(
this.connectorServiceWrite.DeleteConnector({
version,
plugin,
}),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async updateConnector({ plugin, changes }) {
return lastValueFrom(
this.connectorServiceWrite.UpdateConnector({
plugin,
changes,
}),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
}
@@ -0,0 +1,296 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Put,
Post,
Query,
UploadedFile,
UseInterceptors,
Inject,
Headers,
UploadedFiles,
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { ConnectorClientService } from './client.service';
import { AddTagDto } from './dtos/add-tag';
import { CreateConnectorDto } from './dtos/create-connector';
import { UploadConnectorsFileDto } from './dtos/create-multiple-connector';
import { DeleteConnectorDto } from './dtos/delete-connector';
import { GetAllDto } from './dtos/get-all';
import { RemoveTagDto } from './dtos/remove-tag';
import { UpdateDto } from './dtos/update';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import {
Authenticated,
RequireAllPermissions,
RequireSomePermission,
} from 'src/authentication/authentication.decorator';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
@ApiTags('connectors')
@ApiBearerAuth()
@Authenticated()
@Controller('connectors')
export class ConnectorController {
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private connectorClientService: ConnectorClientService,
) {
this.logger = dadosferaLogger.logger;
}
@Post()
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async uploadConnector(
@UploadedFile() file,
@Body() body: CreateConnectorDto,
) {
this.logger.info('/upload - Upload Connector Route');
const response = await this.connectorClientService.uploadConnector({
file,
connector: JSON.parse(body.connector),
});
return response;
}
@Post('/uploads')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
@ApiConsumes('multipart/form-data')
@UseInterceptors(FilesInterceptor('files'))
async uploadFile(@UploadedFiles() files) {
this.logger.info('/upload - Upload Connector Route');
const promises = files.map(async (file) => {
return await this.connectorClientService.uploadFile({
file,
name: file.originalname.split('.')[0],
});
});
const response = await Promise.all(promises);
return response;
}
@Post('/multiple-connectors-upload')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async uploadConnectorsFile(@UploadedFile() file: UploadConnectorsFileDto) {
console.log(`/upload`, 'Upload Connector Route');
const response = await this.connectorClientService.uploadConnectorsFile({
file,
});
return response;
}
@Get()
@RequireSomePermission(
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
)
async getAllConnectors(
@Headers('dadosfera-lang') language,
@Query() queries: GetAllDto,
) {
this.logger.info('/GET - getAllConnectors Route');
if (!language) language = 'en-us';
const { search, size, page, ...filters } = queries;
const response: any = await this.connectorClientService.getAllConnectors({
filters: filters || {},
search: search || '',
language: language || 'pt-br',
size: size || 10,
page: page || 1,
});
const connectors = JSON.parse(response.connectors);
return {
message: response.message,
connectors,
};
}
@Get('/tags')
@RequireSomePermission(
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
)
async getConnectorsTags() {
return await this.connectorClientService.getConnectorsTags();
}
@Get('/:plugin')
@RequireSomePermission(
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
)
async getConnector(
@Headers('dadosfera-lang') language,
@Param('plugin') plugin: string,
@Query('version') version: string,
) {
this.logger.info('/GET/:plugin - getConnector Route');
if (!language) language = 'en-us';
const pluginId = `${plugin}-${version}`;
const response: any = await this.connectorClientService.getConnector(
pluginId,
language,
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Get('/:plugin/details')
@RequireSomePermission(
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
)
async getConnectorDetails(
@Headers('dadosfera-lang') language,
@Param('plugin') plugin: string,
@Query('version') version: string,
) {
this.logger.info('/upload - Upload Connector Route');
if (!language) language = 'en-us';
const pluginId = `${plugin}-${version}`;
const response: any = await this.connectorClientService.getConnectorDetails(
pluginId,
language || 'pt-br',
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
@ApiConsumes('multipart/form-data')
async updateConnector(
@Param('plugin') plugin: string,
@Body() body: UpdateDto,
) {
this.logger.info('/upload - Upload Connector Route');
const changes = body;
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin/add-tag')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
async addTagOnConnector(
@Param('plugin') plugin: string,
@Body() body: AddTagDto,
) {
this.logger.info('/upload - Upload Connector Route');
const { tags } = body;
if (!tags || !(typeof tags === 'object') || !tags.length) {
throw new HttpException('Not found tags attributes', 400);
}
const changes = { plugin, tags };
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin/remove-tag')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
async removeTagOnConnector(
@Param('plugin') plugin: string,
@Body() body: RemoveTagDto,
) {
this.logger.info('/upload - Upload Connector Route');
const { tags } = body;
const remove = tags;
if (!remove || !(typeof remove === 'object') || !remove.length) {
throw new HttpException('Not found remove attribute', 400);
}
const changes = { plugin, remove };
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Delete('/:plugin')
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.DELETE)
async deleteConnector(
@Param('plugin') plugin: string,
@Body() { version }: DeleteConnectorDto,
) {
this.logger.info('/upload - Upload Connector Route');
const response: any = await this.connectorClientService.deleteConnector(
plugin,
version,
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { ConnectorClientService } from './client.service';
import { ConnectorClientConfiguration } from './client.config';
import { ConnectorController } from './connector.controller';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
const client = new ConnectorClientConfiguration();
@Module({
controllers: [ConnectorController],
providers: [ConnectorClientService, DadosferaLogger],
imports: [ClientsModule.register([client.providerOptions])],
exports: [ConnectorClientService],
})
export class ConnectorModule {}
+6
View File
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class AddTagDto {
@ApiProperty()
tags: string[];
}
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreateConnectorDto {
@ApiProperty({ format: 'binary' })
file: string;
@ApiProperty()
connector: string;
}
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class UploadConnectorsFileDto {
@ApiProperty({ format: 'binary' })
file: string;
}
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class DeleteConnectorDto {
@ApiProperty()
version: string;
}
+18
View File
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
export class GetAllDto {
@ApiProperty()
search: string;
@ApiProperty()
language: string;
@ApiProperty()
size: string;
@ApiProperty()
page: string;
@ApiProperty()
filters: any;
}
+6
View File
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class RemoveTagDto {
@ApiProperty()
tags: string[];
}
+42
View File
@@ -0,0 +1,42 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateDto {
@ApiProperty({ format: 'binary', required: false })
file?: string;
@ApiProperty()
version: string;
@ApiProperty({ required: false })
category?: string;
@ApiProperty({ required: false })
plugin?: string;
@ApiProperty({ required: false })
image?: string;
@ApiProperty({ required: false })
docs?: string;
@ApiProperty({ required: false })
name?: string;
@ApiProperty({
required: false,
description: 'skip_select_entities: boolean skip_select_columns: boolean;',
})
options?: {
skip_select_entities: boolean;
skip_select_columns: boolean;
};
@ApiProperty({
required: false,
type: 'array',
items: {
properties: {},
},
})
connection_controls?: object[];
}
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class UploadFileDto {
@ApiProperty({ format: 'binary' })
file: string;
@ApiProperty()
name: string;
}
+38
View File
@@ -0,0 +1,38 @@
import { credentials } from '@grpc/grpc-js';
import {
ClientsProviderAsyncOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
import {
ProtoPackages,
ProtoPaths,
} from '@dadosfera/protospack-v2/dist/lib/Duc';
export class DucClient {
public name = 'DucClient';
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.DUC_URL,
package: ProtoPackages.WritePackage,
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
protoPath: ProtoPaths.WriteFilePath,
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
},
},
};
providerOptions: ClientsProviderAsyncOptions = {
name: this.name,
...this.config,
};
}
+12 -3
View File
@@ -1,13 +1,22 @@
import { Controller, Get } from '@nestjs/common';
import { Controller, Get, Inject } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { HealthService } from './health.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
@ApiTags('Health')
@Controller('health')
export class HealthController {
constructor(private readonly healthService: HealthService) {}
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private readonly healthService: HealthService,
) {
this.logger = dadosferaLogger.logger;
}
@Get()
check() {
console.log(`/health`, 'check');
return this.healthService.check();
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { HealthController } from './health.controller';
import { HealthService } from './health.service';
@Module({
controllers: [HealthController],
providers: [HealthService, DadosferaLogger],
})
export class HealthModule {}
@@ -1,5 +0,0 @@
describe('PipelinesGrpcServerService', () => {
it('should be defined', () => {
expect(2 + 2).toBe(4);
});
});
+9 -2
View File
@@ -1,9 +1,16 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
@Injectable()
export class HealthService {
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
check() {
console.log('HEALTH');
return { message: 'Ok' };
}
}
+204
View File
@@ -0,0 +1,204 @@
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { Info } from 'protospack/dist/lib/interfaces';
export class GoogleAnalyticsClientSecrets {
@ApiProperty()
type: string;
@ApiProperty()
project_id: string;
@ApiProperty()
private_key_id: string;
@ApiProperty()
private_key: string;
@ApiProperty()
client_email: string;
@ApiProperty()
client_id: string;
@ApiProperty()
auth_uri: string;
@ApiProperty()
token_uri: string;
@ApiProperty()
auth_provider_x509_cert_url: string;
@ApiProperty()
client_x509_cert_url: string;
}
export class OauthObject {
@ApiPropertyOptional()
get_tokens_url: string;
@ApiPropertyOptional()
get_tokens_url_params: string;
@ApiPropertyOptional()
get_tokens_set_response: Record<string, any>;
@ApiPropertyOptional()
content_type: string;
}
export class InputOptions {
@ApiPropertyOptional()
oauth: OauthObject;
@ApiPropertyOptional()
skip_select_columns: true;
@ApiPropertyOptional()
skip_select_entities: false;
@ApiPropertyOptional()
skip_transformation: true;
}
export class FileFormatParams {
@ApiProperty()
sep: string;
@ApiProperty()
header: boolean;
@ApiProperty()
encoding: string;
}
export class AuthParameters {
@ApiProperty()
aws_access_key_id: string;
@ApiProperty()
aws_secret_access_key: string;
}
export class CredentialsJdbc {
@ApiPropertyOptional()
jdbc_user: string;
@ApiPropertyOptional()
jdbc_password: string;
@ApiPropertyOptional()
database: string;
@ApiPropertyOptional()
endpoint: string;
@ApiPropertyOptional()
port: string;
@ApiPropertyOptional()
engine: string;
@ApiPropertyOptional()
schema: string;
}
export class Credentials extends CredentialsJdbc {
@ApiProperty()
connection_type: string;
@ApiPropertyOptional()
client_aws_access_key_id: string;
@ApiPropertyOptional()
client_aws_secret_access_key: string;
@ApiPropertyOptional()
client_bucket: string;
@ApiPropertyOptional()
file_to_extract: string;
@ApiPropertyOptional()
file_format_params: FileFormatParams;
@ApiPropertyOptional()
view_id: string;
@ApiPropertyOptional()
client_secrets: GoogleAnalyticsClientSecrets;
@ApiPropertyOptional()
start_date: string;
@ApiPropertyOptional()
end_date: string;
@ApiPropertyOptional()
oauth_code: string;
}
export class Column {
@ApiProperty()
name: string;
@ApiProperty()
type: string;
}
export class TableColumns {
@ApiProperty()
name: string;
@ApiProperty()
columns: string[];
@ApiProperty()
references: Column[];
}
export class Input {
@ApiProperty()
id: string;
@ApiProperty({ type: [TableColumns] })
tables: TableColumns[];
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiProperty()
plugin: string;
@ApiPropertyOptional()
type?: string;
@ApiProperty()
created_at: string;
@ApiProperty()
updated_at: string;
//deprecated attributes (for retro compatibility)
@ApiPropertyOptional({ deprecated: true })
category: string;
@ApiPropertyOptional({ deprecated: true })
credentials: Credentials;
@ApiPropertyOptional({ deprecated: true })
options: InputOptions;
@ApiPropertyOptional({ deprecated: true })
info: Info;
@ApiPropertyOptional({ deprecated: true })
cron: string;
@ApiPropertyOptional({ deprecated: true })
source_bucket: string;
@ApiPropertyOptional({ deprecated: true })
source_prefix: string;
@ApiPropertyOptional({ deprecated: true })
auth_parameters: AuthParameters;
@ApiPropertyOptional({ deprecated: true })
file_format_params: FileFormatParams;
}
export class GetAvailableEntitiesReq {
@ApiProperty()
plugin: string;
}
export class GetAvailableEntitiesRes {
@ApiProperty()
entities: string[];
}
export class TestConnectionGetColumnsReq {
@ApiProperty()
plugin: string;
@ApiProperty()
tables: string[];
@ApiPropertyOptional()
credentials: CredentialsJdbc;
@ApiPropertyOptional()
id: string;
@ApiPropertyOptional()
info: Info;
}
export class TestConnectionGetColumnsRes {
@ApiProperty({ type: [TableColumns] })
tables: TableColumns[];
}
export class TestConnectionReq {
@ApiProperty()
plugin: string;
@ApiProperty()
credentials: CredentialsJdbc;
@ApiPropertyOptional()
info: Info;
}
export class TestConnectionRes {
@ApiProperty()
connection_state: boolean;
@ApiProperty()
total_entities: number;
@ApiProperty()
database_tables: string[];
}
export class CreateInputReq extends OmitType(Input, [
'id',
'created_at',
'updated_at',
]) {}
@@ -1,3 +1,5 @@
import { Info } from 'protospack/dist/lib/interfaces';
interface Values {
jdbc_user: string;
jdbc_password: string;
@@ -9,30 +11,6 @@ interface Values {
schema: string;
}
interface Cron {
hours: string[];
hour_interval: boolean;
hour_resourse: boolean;
hour_resourse_value: number;
month_day: string[];
month_day_interval: boolean;
month_day_resourse: boolean;
month_day_resourse_value: number;
month: string[];
month_interval: boolean;
month_resourse: boolean;
month_resourse_value: number;
week_days: string[];
week_days_interval: boolean;
week_days_resourse: boolean;
week_days_resourse_value: number;
}
export interface Info {
costumer_id: string;
user_id: string;
}
export interface ICreateInputRequest {
cron: string;
name: string;
@@ -54,6 +32,7 @@ export interface UpdateInputRequest {
values: Values;
operation: string;
info: Info;
tables: any[];
}
export interface ITestConnectionRequest {
@@ -0,0 +1,38 @@
import { Input } from '@dadosfera/protospack-v2';
import { credentials } from '@grpc/grpc-js';
import {
ClientProviderOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
export class InputsGrpcClient {
public readonly name = 'InputsGrpcClient';
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [
Input.ProtoPackages.WritePackage,
Input.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
protoPath: [
Input.ProtoPaths.WriteFilePath,
Input.ProtoPaths.ReadFilePath,
],
loader: {
keepCase: true,
enums: String,
defaults: false,
},
},
};
providerOptions: ClientProviderOptions = {
name: this.name,
...this.config,
};
}
+125 -32
View File
@@ -3,48 +3,137 @@ import {
Controller,
Delete,
Get,
Inject,
Param,
Patch,
Post,
Put,
} from '@nestjs/common';
import { InputsService } from './inputs.service';
import { InputsClientService } from 'src/clients/inputs/client.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import {
ICreateInputRequest,
UpdateInputRequest,
} from 'src/clients/inputs/interfaces';
CreateInputReq,
GetAvailableEntitiesReq,
GetAvailableEntitiesRes,
Input,
TestConnectionGetColumnsReq,
TestConnectionGetColumnsRes,
TestConnectionReq,
TestConnectionRes,
} from './dtos/input.model';
import { UpdateInputRequest } from './dtos/old_interfaces';
import { RequestUser, User } from 'src/authentication/user.decorator';
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
@ApiTags('inputs')
@Controller('inputs')
@AuthenticateCondition((req, user) => {
switch (req.method) {
case 'POST':
case 'PUT':
case 'PATCH':
return (
user.permissions.includes(
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
) ||
user.permissions.includes(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid)
);
default:
return (
user.permissions.includes(
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
) ||
user.permissions.includes(
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
) ||
user.permissions.includes(PERMISSIONS_GROUPS.PIPELINE.permissions.GET.seqid)
);
}
})
export class InputsController {
constructor(private inputsClientService: InputsClientService) {}
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private inputService: InputsService,
) {
this.logger = dadosferaLogger.logger;
}
@Get('available-entities/:plugin')
@ApiOkResponse({ type: GetAvailableEntitiesRes })
async getAvailableEntities(
@Body() body,
@Param() params: GetAvailableEntitiesReq,
) {
const { info } = body;
const { plugin } = params;
return await this.inputService.getAvailableEntities({ info, plugin });
}
@Post('/test-connection')
async testConnection(@Body() data) {
console.log(`/test-connection`, 'ON TEST CONNECTION ROUTE');
@ApiOkResponse({ type: TestConnectionRes })
async testConnection(@Body() data: TestConnectionReq) {
this.logger.info(`/test-connection - ON TEST CONNECTION ROUTE`, {
user: data.info.user_id,
customer: data.info.customer,
});
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.testConnection(data);
const response = await this.inputService.testConnection(data);
return response;
}
@Post('/test-connection/get-columns')
@ApiOkResponse({ type: TestConnectionGetColumnsRes })
async getColumns(@Body() data: TestConnectionGetColumnsReq) {
this.logger.info(
`/test-connection/get-columns - ON TEST CONNECTION GET COLUMNS ROUTE`,
{
user: data.info.user_id,
customer: data.info.customer,
},
);
const response = await this.inputService.getColumns(data);
return response;
}
@Post()
async create(@Body() createInputDto: ICreateInputRequest) {
console.log(`/input`, 'ON CREATE ROUTE');
@ApiOkResponse({ type: Input })
async create(@Body() body: CreateInputReq, @User() user: RequestUser) {
const info: Info = {
user_id: user.user_id,
customer: user.customer_name,
customer_id: user.customer_id,
};
this.logger.info(`/input - ON CREATE ROUTE`, {
user: info.user_id,
customer: info.customer,
});
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.create(createInputDto);
const response = await this.inputService.create({ body, info });
return response;
}
@Get()
async findAll(@Body() body) {
console.log(`/input`, 'ON FIND ALL ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.findAll(body);
async findAll(@User() user: RequestUser) {
const { user_id, customer_id, customer_name: customer } = user;
this.logger.info(`/input - ON FIND ALL ROUTE`, {
user: user.user_id,
customer,
});
const info: Info = {
customer,
customer_id,
user_id,
};
const response = await this.inputService.findAll(info);
return response;
}
@@ -52,24 +141,27 @@ export class InputsController {
@Get('/:id')
async findOne(@Body() body, @Param() params) {
const { id } = params;
console.log(`/input/${id}`, 'ON FIND ONE ROUTE');
const inputService = new InputsService(this.inputsClientService);
this.logger.info(`/input/${id} - ON FIND ONE ROUTE`, {
user: body.info.user_id,
customer: body.info.customer,
});
const response = await inputService.findOne({ id, ...body });
const response = await this.inputService.findOne({ id, ...body });
return response;
}
@Put(':id')
@Patch(':id')
async update(@Body() updateInputDto: UpdateInputRequest, @Param() params) {
const { id } = params;
const { info } = updateInputDto;
this.logger.info(`/input/${id} - ON UPDATE ROUTE`, {
user: info.user_id,
customer: info.customer_id,
});
delete updateInputDto.info;
console.log(`/input/${id}`, 'ON UPDATE ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.update(id, updateInputDto, info);
const response = await this.inputService.update(id, updateInputDto, info);
return response;
}
@@ -78,11 +170,12 @@ export class InputsController {
async delete(@Body() data, @Param() params) {
const { id } = params;
console.log(`/input/${id}`, 'ON DELETE ROUTE');
this.logger.info(`/input/${id} - ON DELETE ROUTE`, {
user: data.info.user_id,
customer: data.info.customer,
});
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.remove({ id, ...data });
const response = await this.inputService.remove({ id, ...data });
return response;
}
+16
View File
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { InputsGrpcClient } from './inputs-client.config';
import { InputsController } from './inputs.controller';
import { InputsService } from './inputs.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
const client = new InputsGrpcClient();
@Module({
controllers: [InputsController],
providers: [InputsService, DadosferaLogger],
imports: [ClientsModule.register([client.providerOptions])],
exports: [InputsService],
})
export class InputsModule {}
@@ -1,5 +0,0 @@
describe('PipelinesGrpcServerService', () => {
it('should be defined', () => {
expect(2 + 2).toBe(4);
});
});
+282 -27
View File
@@ -1,38 +1,251 @@
import { Body, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InputsClientService } from 'src/clients/inputs/client.service';
import {
ICreateInputRequest,
IIdRequest,
Info,
} from 'src/clients/inputs/interfaces';
ForbiddenException,
HttpException,
HttpStatus,
Inject,
Injectable,
} from '@nestjs/common';
import { Timeout } from '@nestjs/schedule';
import CronParser, { CronExpression } from 'cron-parser';
import { ClientGrpc } from '@nestjs/microservices';
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
import { lastValueFrom } from 'rxjs';
import { objectCamelToSnake } from 'src/utils/CaseConverter';
import { IIdRequest, UpdateInputRequest } from './dtos/old_interfaces';
import { Input } from '@dadosfera/protospack-v2';
import {
InputCreateGenericRequest,
InputCreateS3Request,
InputNewCreateRequest,
TestConnectionGetColumnsRequest,
TestConnectionRequest,
} from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/messages';
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
import { CreateInputReq } from './dtos/input.model';
@Injectable()
//TODO refactor this class to remove OLD_inputClient
export class InputsService {
constructor(private inputClient: InputsClientService) {}
logger: DadosferaLogger;
inputWriteService: Input.WriteService.InputWriteService;
inputReadService: Input.ReadService.InputReadService;
async create(@Body() data: ICreateInputRequest) {
try {
const createInputResponse = await this.inputClient.create(data);
return createInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
@Inject('InputsGrpcClient') private readonly grpcClient: ClientGrpc,
) {
this.logger = dadosferaLogger.logger;
}
async findAll(body) {
try {
const findAllInputResponse = await this.inputClient.findAll(body);
async onModuleInit() {
this.inputWriteService =
this.grpcClient.getService<Input.WriteService.InputWriteService>(
Input.ProtoServices.InputWriteService,
);
this.inputReadService =
this.grpcClient.getService<Input.ReadService.InputReadService>(
Input.ProtoServices.InputReadService,
);
}
return findAllInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
OLD_inputClient = {
newCreate: async (createInputDto: InputNewCreateRequest) => {
this.logger.info('InputClientService - Create');
const input = await lastValueFrom(
this.inputWriteService.InputNewCreate(createInputDto),
);
objectCamelToSnake(input);
return input;
},
createS3Inputs: async (createInputDto: InputCreateS3Request) => {
this.logger.info('InputClientService - Create');
createInputDto;
const createInputResponse = await lastValueFrom(
this.inputWriteService.InputCreateS3(createInputDto),
).catch((e) => {
throw new HttpException(e.details, 500);
});
this.logger.info('Done');
objectCamelToSnake(createInputResponse);
return createInputResponse;
},
findOne: async (data: IIdRequest) => {
this.logger.info('InputClientService - FindOne');
const findOneInputResponse = await lastValueFrom(
this.inputReadService.InputFindOne(data),
).catch((e) => {
this.logger.error(e.details);
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
});
objectCamelToSnake(findOneInputResponse);
return findOneInputResponse;
},
update: async (updateInputDTO: UpdateInputRequest) => {
this.logger.info('InputClientService - Update');
const updateInputResponse = await lastValueFrom(
this.inputWriteService.InputUpdate(updateInputDTO),
);
return updateInputResponse;
},
remove: async (idRequest: IIdRequest) => {
this.logger.info('InputClientService - Remove');
const removeInputResponse = await lastValueFrom(
this.inputWriteService.InputRemove(idRequest),
);
return removeInputResponse;
},
testConnection: async (data: TestConnectionRequest) => {
this.logger.info('InputClientService - TestConnection');
const testConnectionResponse = await lastValueFrom(
this.inputReadService.NewTestConnection(data),
);
return testConnectionResponse;
},
getColumns: async (data: TestConnectionGetColumnsRequest) => {
this.logger.info('InputClientService - TestConnection/Get-Columns');
const getColumnsResponse = await new Promise((resolve, reject) => {
this.inputReadService.GetColumns(data).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
reject(err);
},
complete() {
// console.log('done');
},
});
})
.then((res) => {
this.logger.info('Done');
return res;
})
.catch((err) => {
this.logger.error(err.message);
throw new Error(err);
});
return getColumnsResponse;
},
getAvailableEntities: async (data) => {
this.logger.info('InputClientService - GetAvailableEntities');
data;
const response = await lastValueFrom(
this.inputReadService.GetAvailableEntities(data),
).catch((err) => {
this.logger.info(err.details);
if (err.details && err.details.includes('400'))
throw new HttpException('Plugin inválido', HttpStatus.BAD_REQUEST);
throw new HttpException(err.details, HttpStatus.INTERNAL_SERVER_ERROR);
});
objectCamelToSnake(response);
return response;
},
};
secondsInADay = 60 * 60 * 24;
secondsInAnHour = 60 * 60;
adjustInputPayload(payload) {
if (payload) {
if (payload.input_generic) {
const { credentials } = payload.input_generic;
return {
...payload.input_generic,
credentials: credentials && JSON.parse(credentials),
};
}
if (payload.input_s3) return payload.input_s3;
if (payload.input_jdbc) return payload.input_jdbc;
const { credentials } = payload;
return {
...payload,
credentials: credentials && JSON.parse(credentials),
};
}
return payload;
}
getDifferenceInSeconds(date1: Date, date2: Date) {
const diffInMs = Math.abs(date2.getTime() - date1.getTime());
return diffInMs / 1000;
}
validateCron(data) {
const { info, cron } = data;
const { customer_tier } = info;
if (!cron) return;
let interval: CronExpression;
try {
interval = CronParser.parseExpression(cron);
} catch (error) {
throw new HttpException(
'Intervalo de tempo inválido',
HttpStatus.BAD_REQUEST,
);
}
const nextDate = interval.next().toDate();
const afterNextDate = interval.next().toDate();
const secondsApart = this.getDifferenceInSeconds(nextDate, afterNextDate);
if (customer_tier === 'BASIC' && secondsApart < this.secondsInADay) {
throw new ForbiddenException(
'Intervalo de tempo não pode ser inferior a um dia.',
);
} else if (secondsApart < this.secondsInAnHour) {
throw new ForbiddenException(
'Intervalo de tempo não pode ser inferior a uma hora.',
);
}
}
async create(data: { body: CreateInputReq; info: Info }) {
// this.validateCron(data);
const { body, info } = data;
if (body.info) delete body.info;
const inputCreateGenericRequest: any | InputCreateGenericRequest = {
input: {
...body,
credentials: body.credentials && JSON.stringify(body.credentials),
},
info,
};
const { input } = await lastValueFrom(
this.inputWriteService.InputCreate(inputCreateGenericRequest),
);
const adjustedInput = this.adjustInputPayload(input);
return { input: adjustedInput };
}
async getAvailableEntities(data): Promise<{ entities: string[] }> {
return await this.OLD_inputClient.getAvailableEntities(data);
}
async findAll(info: Info) {
const { inputs } = await lastValueFrom(
this.inputReadService.InputFindAll({ info }),
);
if (!inputs) return { inputs: [] };
const inputsReturn = inputs.map((i) => this.adjustInputPayload(i));
return { inputs: inputsReturn };
}
async findOne(idRequest: IIdRequest) {
try {
const findOneInputResponse = await this.inputClient.findOne(idRequest);
const findOneInputResponse: any = await this.OLD_inputClient.findOne(
idRequest,
);
findOneInputResponse.input = this.adjustInputPayload(
findOneInputResponse.input,
);
return findOneInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
@@ -40,13 +253,17 @@ export class InputsService {
}
async update(id: string, data, info: Info) {
this.validateCron({ ...data, info });
try {
const updateInputResponse = await this.inputClient.update({
const updateInputResponse: any = await this.OLD_inputClient.update({
id,
info,
...data,
});
updateInputResponse.input = this.adjustInputPayload(
updateInputResponse?.input,
);
return updateInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
@@ -55,7 +272,7 @@ export class InputsService {
async remove(idRequest: IIdRequest) {
try {
const removeInputResponse = await this.inputClient.remove(idRequest);
const removeInputResponse = await this.OLD_inputClient.remove(idRequest);
return removeInputResponse;
} catch (err) {
@@ -63,15 +280,53 @@ export class InputsService {
}
}
@Timeout(60000 * 10) // Timeout set for 10 minutes
async testConnection(data) {
try {
const testConnectionInputResponse = await this.inputClient.testConnection(
data,
);
const testConnectionInputResponse =
await this.OLD_inputClient.testConnection(data);
return testConnectionInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
async getColumns(data) {
try {
const testConnectionGetColumnsResponse =
await this.OLD_inputClient.getColumns(data);
return testConnectionGetColumnsResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
generateInputS3Payload(payload) {
const { credentials, plugin, cron } = payload;
if (!credentials) return payload;
const {
client_aws_access_key_id,
client_aws_secret_access_key,
file_format_params,
format_file_params,
client_bucket,
file_to_extract,
} = credentials;
const formatedPayload = {
plugin,
cron,
source_bucket: client_bucket,
source_prefix: file_to_extract,
auth_parameters: {
aws_access_key_id: client_aws_access_key_id,
aws_secret_access_key: client_aws_secret_access_key,
},
file_format_params: file_format_params || format_file_params,
};
return formatedPayload;
}
}
@@ -0,0 +1,172 @@
import {
NetworkConfigEntity,
NetworkConfigToCatalog,
} from '@dadosfera/protospack-v2/dist/lib/NetworkConfig/interfaces/entities';
import {
ApiProperty,
ApiPropertyOptional,
OmitType,
PickType,
} from '@nestjs/swagger';
export enum NetworkConfigTypesEnum {
vpn = 'vpn',
}
export enum NetworkConfigPluginsEnum {
openvpn = 'openvpn',
}
export interface ConnectionsApiNetworkConfig {
config_id: string;
created_at: string;
credentials: {
CERTIFICATE_FILE: string;
OVPN_CREDENTIALS: string;
TLS_AUTH_FILE: string;
OVPN_FILE: string;
};
customer_name: string;
description: string;
name: string;
plugin: string;
secret_id: string;
sensitive_fields: string[];
type: string;
updated_at: string;
}
export class NetworkConfigDto implements NetworkConfigEntity {
@ApiProperty()
id: string;
@ApiProperty()
created_at: string;
@ApiProperty()
updated_at: string;
@ApiProperty()
customer_name: string;
@ApiProperty()
customer_id: string;
@ApiProperty()
properties: any;
@ApiProperty()
user_id: string;
@ApiProperty()
keywords: string;
@ApiProperty()
tags: string;
@ApiProperty()
network_type_id: string;
@ApiProperty()
network_type_name: string;
@ApiProperty({ enum: NetworkConfigTypesEnum })
type: string;
@ApiProperty({ enum: NetworkConfigPluginsEnum })
plugin: string;
@ApiProperty()
name: string;
@ApiProperty()
description: string;
}
export class CreateNetworkConfigReq extends PickType(NetworkConfigDto, [
'network_type_id',
'network_type_name',
'name',
'description',
]) {
@ApiProperty({ format: 'binary' })
ovpn_file: string;
}
export class NetworkConfigOpenVpnProperties {
@ApiProperty({ enum: NetworkConfigPluginsEnum })
plugin: NetworkConfigPluginsEnum;
@ApiProperty()
OVPN_FILE: string;
@ApiPropertyOptional()
CERTIFICATE_FILE?: string;
@ApiPropertyOptional()
OVPN_CREDENTIALS?: string;
@ApiPropertyOptional()
TLS_AUTH_FILE?: string;
}
export class NetworkConfigToCatalogDto implements NetworkConfigToCatalog {
@ApiProperty()
id: string;
@ApiProperty()
customer_id: string;
@ApiProperty()
plugin: string;
@ApiProperty()
type: string;
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiProperty()
created_at: string;
@ApiProperty()
updated_at: string;
@ApiProperty()
network_type_id: string;
@ApiProperty()
network_type_name: string;
}
export class FindAllNetworkConfigsRes {
@ApiProperty({ type: [NetworkConfigToCatalogDto] })
network_configs: NetworkConfigToCatalogDto[];
@ApiProperty()
message: string;
}
export class FindNetworkConfigByidRes {
@ApiProperty()
network_config: NetworkConfigToCatalogDto;
}
export class FindNetworkConfigByidDetails extends PickType(NetworkConfigDto, [
'id',
'name',
'description',
'type',
'network_type_id',
'customer_name',
]) {
@ApiProperty()
properties: NetworkConfigOpenVpnProperties | string;
}
export class FindNetworkConfigByidDetailsRes {
@ApiProperty()
network_config: FindNetworkConfigByidDetails;
}
export class CreateNetworkConfigRes {
@ApiProperty()
network_config: NetworkConfigToCatalogDto;
}
export class UpdateNetworkConfigReq {
@ApiProperty()
name: string;
@ApiProperty()
description: string;
}
export class UpdateNetworkConfigRes {
@ApiProperty()
network_config: NetworkConfigToCatalogDto;
}
export class DeleteNetworkConfigRes {
@ApiProperty()
message: string;
}
@@ -0,0 +1,38 @@
import { credentials } from '@grpc/grpc-js';
import {
ClientProviderOptions,
GrpcOptions,
Transport,
} from '@nestjs/microservices';
import { NetworkConfig } from '@dadosfera/protospack-v2';
export class NetworkConfigGrpcClient {
public name = 'NetworkConfigGrpcClient';
private config: GrpcOptions = {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [
NetworkConfig.ProtoPackages.ReadPackage,
NetworkConfig.ProtoPackages.WritePackage,
],
credentials:
process.env.ENV === 'local' ? undefined : credentials.createSsl(),
protoPath: [
NetworkConfig.ProtoPaths.ReadFilePath,
NetworkConfig.ProtoPaths.WriteFilePath,
],
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
},
},
};
providerOptions: ClientProviderOptions = {
name: this.name,
...this.config,
};
}
@@ -0,0 +1,146 @@
import {
Body,
Controller,
Delete,
Get,
Headers,
Inject,
Param,
Patch,
Post,
Query,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { NetworkConfigService } from './network-config.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { ApiConsumes, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { RequestUser, User } from 'src/authentication/user.decorator';
import {
CreateNetworkConfigReq,
CreateNetworkConfigRes,
DeleteNetworkConfigRes,
FindAllNetworkConfigsRes,
UpdateNetworkConfigReq,
UpdateNetworkConfigRes,
} from './dto/network-config';
import { FileInterceptor } from '@nestjs/platform-express';
@ApiTags('Network Config')
@Controller('network-config')
export class NetworkConfigController {
logger: any;
constructor(
private networkConfigService: NetworkConfigService,
@Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
@Get()
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
async findAllNetworkConfigs(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language,
@Query() queries,
) {
this.logger.info('GET /network-config', {
user: user.user_id,
customer: user.customer_name,
});
if (!language) language = 'en-us';
const { user_id, customer_id, customer_name } = user;
const { search, size, page, ...filters } = queries;
return this.networkConfigService.findAllNetworkConfigs({
body: { search, size, page, filters },
info: { user_id, customer_id, customer_name, language },
});
}
@Get(':id')
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
async findNetworkConfigById(
@User() user: RequestUser,
@Param('id') id: string,
) {
this.logger.info('GET /network-config/:id', {
user: user.user_id,
customer: user.customer_name,
});
return this.networkConfigService.findNetworkConfigById(id);
}
@Get(':id/details')
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
async findNetworkConfigByIdDetails(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language,
@Param('id') id: string,
) {
this.logger.info('GET /network-config/:id', {
user: user.user_id,
customer: user.customer_name,
});
if (!language) language = 'en-us';
return this.networkConfigService.findNetworkConfigByIdDetails(id, {
customer_name: user.customer_name,
customer_id: user.customer_id,
language,
});
}
@Post()
@ApiOkResponse({ type: CreateNetworkConfigRes })
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('ovpn_file'))
async createNetworkConfigs(
@User() user: RequestUser,
@UploadedFile() file: Express.Multer.File,
@Body() body: CreateNetworkConfigReq,
) {
this.logger.info('POST /network-config', {
user: user.user_id,
customer: user.customer_name,
});
const { customer_id, customer_name, user_id } = user;
return this.networkConfigService.createNetworkConfig(body, file, {
customer_name,
customer_id,
user_id,
});
}
@Patch(':id')
@ApiOkResponse({ type: UpdateNetworkConfigRes })
async updateNetworkConfig(
@User() user: RequestUser,
@Param('id') network_config_id: string,
@Body() body: UpdateNetworkConfigReq,
) {
this.logger.info('PATCH /network-config/:id', {
user: user.user_id,
customer: user.customer_name,
});
return this.networkConfigService.updateNetworkConfig(
body,
network_config_id,
);
}
@Delete(':id')
@ApiOkResponse({ type: DeleteNetworkConfigRes })
async deleteNetworkConfig(
@User() user: RequestUser,
@Param('id') network_config_id: string,
) {
this.logger.info('DELETE /network-config', {
user: user.user_id,
customer: user.customer_name,
});
return this.networkConfigService.deleteNetworkConfig(network_config_id, {
customer_name: user.customer_name,
customer_id: user.customer_id,
});
}
}
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { NetworkConfigController } from './network-config.controller';
import { NetworkConfigService } from './network-config.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { ClientsModule } from '@nestjs/microservices';
import { NetworkConfigGrpcClient } from './network-config-client.config';
const client = new NetworkConfigGrpcClient();
@Module({
controllers: [NetworkConfigController],
providers: [NetworkConfigService, DadosferaLogger],
imports: [ClientsModule.register([client.providerOptions])],
})
export class NetworkConfigModule {}
@@ -0,0 +1,158 @@
import { Metadata } from '@grpc/grpc-js';
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { NetworkConfig } from '@dadosfera/protospack-v2';
import { lastValueFrom } from 'rxjs';
import {
CreateNetworkConfigReq,
CreateNetworkConfigRes,
DeleteNetworkConfigRes,
FindAllNetworkConfigsRes,
FindNetworkConfigByidDetailsRes,
FindNetworkConfigByidRes,
UpdateNetworkConfigReq,
UpdateNetworkConfigRes,
} from './dto/network-config';
import { NetworkConfigGrpcClient } from './network-config-client.config';
@Injectable()
export class NetworkConfigService {
private networkConfigReadClient: NetworkConfig.ReadService.NetworkConfigReadService;
private networkConfigWriteClient: NetworkConfig.WriteService.NetworkConfigWriteService;
constructor(
@Inject(NetworkConfigGrpcClient.name)
private readonly grpcClient: ClientGrpc,
) {
this.networkConfigReadClient =
grpcClient.getService<NetworkConfig.ReadService.NetworkConfigReadService>(
NetworkConfig.ProtoServices.NetworkConfigReadService,
);
this.networkConfigWriteClient =
grpcClient.getService<NetworkConfig.WriteService.NetworkConfigWriteService>(
NetworkConfig.ProtoServices.NetworkConfigWriteService,
);
}
async findAllNetworkConfigs({
body,
info,
}): Promise<FindAllNetworkConfigsRes> {
const { search, page, size, filters } = body;
const { language, customer_id, customer_name, user_id } = info;
const meta = new Metadata();
meta.add('language', language);
meta.add('customer_id', customer_id);
meta.add('customer_name', customer_name);
meta.add('user_id', user_id);
return lastValueFrom(
this.networkConfigReadClient.NetworkConfigFindAll(
{ search, filters: JSON.stringify(filters), page, size },
meta,
),
);
}
async findNetworkConfigById(id: string): Promise<FindNetworkConfigByidRes> {
const { network_config } = await lastValueFrom(
this.networkConfigReadClient.NetworkConfigFindOneById({
id,
}),
);
return { network_config };
}
async findNetworkConfigByIdDetails(
id: string,
info?,
): Promise<FindNetworkConfigByidDetailsRes> {
const { customer_name, customer_id, language } = info;
const meta = new Metadata();
meta.add('customer_name', customer_name);
meta.add('customer_id', customer_id);
meta.add('language', language);
const { network_config } = await lastValueFrom(
this.networkConfigReadClient.NetworkConfigFindOneDetailsById(
{ id },
meta,
),
);
Object.assign(network_config, {
properties: JSON.parse(network_config.properties),
});
return { network_config };
}
async createNetworkConfig(
body: CreateNetworkConfigReq,
file: Express.Multer.File,
{ customer_name, customer_id, user_id },
): Promise<CreateNetworkConfigRes> {
const { description, name, network_type_id, network_type_name } = body;
const type = 'vpn';
const plugin = 'openvpn';
const OVPN_FILE_STRING = Buffer.from(file.buffer).toString();
const meta = new Metadata();
meta.add('customer_id', customer_id);
meta.add('customer_name', customer_name);
meta.add('user_id', user_id);
const { network_config } = await lastValueFrom(
this.networkConfigWriteClient.NetworkConfigCreate(
{
description,
name,
properties: JSON.stringify({
OVPN_FILE: OVPN_FILE_STRING,
plugin,
}),
type,
network_type_id,
network_type_name,
plugin,
},
meta,
),
);
return { network_config };
}
async updateNetworkConfig(
body: UpdateNetworkConfigReq,
network_config_id: string,
): Promise<UpdateNetworkConfigRes> {
const { description, name } = body;
const { network_config } = await lastValueFrom(
this.networkConfigWriteClient.NetworkConfigUpdate({
id: network_config_id,
description,
name,
}),
);
return { network_config };
}
async deleteNetworkConfig(
network_config_id: string,
{ customer_name, customer_id },
): Promise<DeleteNetworkConfigRes> {
const meta = new Metadata();
meta.add('customer_id', customer_id);
meta.add('customer_name', customer_name);
return lastValueFrom(
this.networkConfigWriteClient.NetworkConfigDelete(
{ id: network_config_id },
meta,
),
);
}
}
+167
View File
@@ -0,0 +1,167 @@
import { Controller, Get, Redirect, Req, UseGuards } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { ConnectionClientService } from '../connection/client.service';
import jwt from 'jsonwebtoken';
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
@ApiTags('oauth')
@Controller('oauth')
export class OauthController {
redirectUrl: string;
constructor(
private connectionService: ConnectionClientService,
private logger: DadosferaLogger,
) {
switch (process.env.ENV) {
case 'dev':
case 'stg':
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/coletix/auth-callback`;
break;
case 'prd':
this.redirectUrl = `https://app.dadosfera.ai/coletix/auth-callback`;
break;
default:
this.redirectUrl = `http://localhost:4200/coletix/auth-callback`;
}
}
@Get('hubspot')
@UseGuards(AuthGuard('hubspot'))
async oauthHubspot() {
return true;
}
@Get('hubspot/callback')
@UseGuards(AuthGuard('hubspot'))
@Redirect()
async oauthHubspotCallback(@Req() req) {
return await this.callback(req);
}
@Get('googleads')
@UseGuards(AuthGuard('google'))
async oauthGoogleAds() {
return true;
}
@Get('googleads/callback')
@UseGuards(AuthGuard('google'))
@Redirect()
async oauthGoogleAdsCallback(@Req() req) {
return await this.callback(req);
}
@Get('google-sheets')
@UseGuards(AuthGuard('google'))
async oauthGoogleSheets() {
return true;
}
@Get('google-sheets/callback')
@UseGuards(AuthGuard('google'))
@Redirect()
async oauthGoogleSheetsCallback(@Req() req) {
return await this.callback(req);
}
@Get('facebook')
@UseGuards(AuthGuard('facebook'))
async oauthFacebook() {
return true;
}
@Get('facebook/callback')
@UseGuards(AuthGuard('facebook'))
@Redirect()
async oauthFacebookCallback(@Req() req) {
return await this.callback(req);
}
@Get('mailchimp')
@UseGuards(AuthGuard('mailchimp'))
async oauthMailchimp() {
return true;
}
@Get('mailchimp/callback')
@UseGuards(AuthGuard('mailchimp'))
@Redirect()
async oauthMailchimpCallback(@Req() req) {
return await this.callback(req);
}
@Get('salesforce')
@UseGuards(AuthGuard('salesforce'))
async oauthSalesforce() {
return true;
}
@Get('salesforce/callback')
@UseGuards(AuthGuard('salesforce'))
@Redirect()
async oauthSalesforceCallback(@Req() req) {
return await this.callback(req);
}
async callback(@Req() req) {
const { error, state } = req.query;
const url = new URL(this.redirectUrl);
if (error) {
this.logger.error(error);
url.searchParams.set('error', 'Erro ao autorizar');
url.searchParams.set(
'error_description',
`Credenciais inválidas. Erro: [${error}]`,
);
return { url: url.href };
}
const { authInfo } = req;
if (!authInfo) {
this.logger.error('No authInfo', req);
url.searchParams.set('error', 'Erro ao autorizar');
url.searchParams.set('error_description', `Credenciais inválidas.`);
return { url: url.href };
}
const { accessToken: access_token, refreshToken: refresh_token } = authInfo;
let connectionInfo: any;
try {
connectionInfo = jwt.verify(state as string, process.env.JWT_PRIVATE_KEY);
} catch (error) {
this.logger.error('Invalid state JWT', { state, req, error });
url.searchParams.set('error', 'Erro ao autorizar');
url.searchParams.set(
'error_description',
'Ocorreu um erro ao autorizá-lo. Por favor tente novamente',
);
return { url: url.href };
}
connectionInfo.properties.access_token = access_token;
connectionInfo.properties.refresh_token = refresh_token;
const { customer_id, customer_name, user_id } = connectionInfo;
try {
const response = await this.connectionService.createConnection(
connectionInfo,
PackTheMetadata({ customer_id, customer_name, user_id }),
);
url.searchParams.set('connection_id', response.connection.id);
return { url: url.href };
} catch (error) {
this.logger.error(error);
url.searchParams.set('error', 'Erro ao autorizar');
url.searchParams.set(
'error_description',
'Ocorreu um erro interno ao autorizá-lo. Por favor tente novamente mais tarde',
);
return { url: url.href };
}
}
}
+29
View File
@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { OauthController } from './oauth.controller';
import { getOauthSecrets } from '../../utils/OauthSecrets';
import { HubspotStrategy } from './passport-strategies/hubspot-strategy';
import { FacebookStrategy } from './passport-strategies/facebook-strategy';
import { GoogleStrategy } from './passport-strategies/google-strategy';
import { MailChimpStrategy } from './passport-strategies/mailchimp-strategy';
import { SalesforceStrategy } from './passport-strategies/salesforce-strategy';
import { ConnectionModule } from '../connection/connection.module';
import { OauthService } from './oauth.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
@Module({
providers: [
DadosferaLogger,
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
HubspotStrategy,
FacebookStrategy,
GoogleStrategy,
MailChimpStrategy,
SalesforceStrategy,
OauthService,
],
controllers: [OauthController],
imports: [ConnectionModule],
})
export class OauthModule {}
+51
View File
@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { ConnectionDto } from '../connection/dtos/connection';
import jwt from 'jsonwebtoken';
import { PartialType } from '@nestjs/swagger';
class PartialConnection extends PartialType(ConnectionDto) {}
@Injectable()
export class OauthService {
generateStateFromRequest(prevState, req, type): string {
if (req.route.path.includes('callback')) {
return prevState;
}
const plugin: string = req.route.path.replace('/oauth/', '');
const { query } = req;
const {
name,
description,
connector_id,
connector_name,
image_url,
user_id,
customer_id,
customer_name,
} = query;
const properties = { plugin, credentials_type: 'oauth' };
for (const key in query) {
if (key.includes('properties.')) {
const propertyKey = key.split('.')[1];
properties[propertyKey] = query[key];
}
}
const newConnection: PartialConnection = {
name,
description,
connector_id,
connector_name,
image_url,
user_id,
customer_id,
customer_name,
type,
properties,
plugin,
};
return jwt.sign({ ...newConnection }, process.env.JWT_PRIVATE_KEY);
}
}
@@ -0,0 +1,50 @@
import {
Profile,
Strategy,
StrategyOption,
VerifyFunction,
} from 'passport-facebook';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { Request } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { ParsedQs } from 'qs';
import { OauthService } from '../oauth.service';
@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
private oauthService: OauthService,
) {
const options: StrategyOption = {
clientID: oauthSecrets.facebook.client_id,
clientSecret: oauthSecrets.facebook.client_secret,
callbackURL: oauthSecrets.facebook.redirect_uri,
};
const verify: VerifyFunction = (
accessToken: string,
refreshToken: string,
profile: Profile,
done: (error: any, user?: any, info?: any) => void,
) => {
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
authenticate(
req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
options?: any,
): void {
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'application',
);
options.scope = 'ads_read ads_management';
super.authenticate(req, options);
}
}
@@ -0,0 +1,77 @@
import { Profile, Strategy, StrategyOptions } from 'passport-google-oauth20';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { OauthService } from '../oauth.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
redirect_uri: string;
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
private oauthService: OauthService,
) {
const options: StrategyOptions = {
clientID: oauthSecrets.google.client_id,
clientSecret: oauthSecrets.google.client_secret,
callbackURL: '/oauth/googleads/callback',
};
const verify = (
accessToken: string,
refreshToken: string,
profile: Profile,
done,
) => {
console.log('verify');
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
switch (process.env.ENV) {
case 'dev':
case 'stg':
this.redirect_uri = `https://maestro.${process.env.ENV}.dadosfera.ai/oauth`;
break;
case 'prd':
this.redirect_uri = `https://maestro.dadosfera.ai/oauth`;
break;
default:
this.redirect_uri = `/oauth`;
break;
}
}
authenticate(req, options) {
const plugin: string = req.route.path
.replace('/oauth/', '')
.replace('/callback', '');
options.scope = ['https://www.googleapis.com/auth/userinfo.email'];
options.callbackURL = `${this.redirect_uri}/${plugin}/callback`;
switch (plugin) {
case 'googleads':
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'application',
);
options.scope.push('https://www.googleapis.com/auth/adwords');
break;
case 'google-sheets':
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'file',
);
options.scope.push(
'https://www.googleapis.com/auth/drive.metadata.readonly',
'https://www.googleapis.com/auth/spreadsheets.readonly',
);
break;
}
options.accessType = 'offline';
options.prompt = 'consent';
super.authenticate(req, options);
}
}
@@ -0,0 +1,37 @@
import { Strategy } from 'passport-hubspot-oauth2';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { OauthService } from '../oauth.service';
@Injectable()
export class HubspotStrategy extends PassportStrategy(Strategy) {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
private oauthService: OauthService,
) {
super(
{
clientID: oauthSecrets.hubspot.client_id,
clientSecret: oauthSecrets.hubspot.client_secret,
callbackURL: oauthSecrets.hubspot.redirect_uri,
scope:
'automation business-intelligence oauth forms integration-sync sales-email-read crm.lists.read crm.objects.contacts.read crm.schemas.contacts.read crm.objects.companies.read crm.objects.deals.read crm.schemas.companies.read crm.schemas.deals.read crm.objects.owners.read crm.objects.quotes.read crm.schemas.quotes.read crm.objects.line_items.read crm.schemas.line_items.read',
passReqToCallback: true,
},
(accessToken, refreshToken, tokenInfo, profile, done) => {
return done(null, profile, { accessToken, refreshToken, tokenInfo });
},
);
}
authenticate(req, options) {
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'application',
);
super.authenticate(req, options);
}
}
@@ -0,0 +1,41 @@
import { Strategy } from 'passport-mailchimp';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable, Req } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { OauthService } from '../oauth.service';
@Injectable()
export class MailChimpStrategy extends PassportStrategy(Strategy, 'mailchimp') {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
private oauthService: OauthService,
) {
const options = {
clientID: oauthSecrets.mailchimp.client_id,
clientSecret: oauthSecrets.mailchimp.client_secret,
callbackURL: oauthSecrets.mailchimp.redirect_uri,
scope: '',
};
const verify = (
accessToken: string,
refreshToken: string,
profile: any,
done,
) => {
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
authenticate(@Req() req, options) {
if (!req.route.path.includes('callback'))
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'application',
);
super.authenticate(req, options);
}
}
@@ -0,0 +1,52 @@
import { Strategy } from 'passport-forcedotcom';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { OauthService } from '../oauth.service';
@Injectable()
export class SalesforceStrategy extends PassportStrategy(
Strategy,
'salesforce',
) {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
private oauthService: OauthService,
) {
const options = {
clientID: oauthSecrets.salesforce.client_id,
clientSecret: oauthSecrets.salesforce.client_secret,
callbackURL: oauthSecrets.salesforce.redirect_uri,
scope: [
'wave_api',
'chatter_api',
'chatbot_api',
'cdp_ingest_api',
'cdp_profile_api',
'api',
'pardot_api',
'cdp_segment_api',
'cdp_query_api',
'refresh_token',
'offline_access',
],
};
const verify = ({ params }, refreshToken: string, profile: any, done) => {
return done(null, profile, {
accessToken: params.access_token,
refreshToken,
});
};
super(options, verify);
}
authenticate(req, options) {
options.state = this.oauthService.generateStateFromRequest(
options.state,
req,
'application',
);
super.authenticate(req, options);
}
}
@@ -1,73 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { Payload } from '@nestjs/microservices';
import { OutputsClientService } from 'src/clients/outputs/client.service';
import { OutputsService } from './outputs.service';
@Controller('outputs')
export class OutputsController {
constructor(private outputsClientService: OutputsClientService) {}
@Post()
async create(@Body() createOutputDto) {
console.log(process.env.DEV_URL + `/output`, 'ON CREATE ROUTE');
const outputService = new OutputsService(this.outputsClientService);
const response = await outputService.create(createOutputDto);
return response;
}
@Get()
async findAll(@Body() data) {
console.log(process.env.DEV_URL + `/output`, 'ON Find All ROUTE');
const outputService = new OutputsService(this.outputsClientService);
const response = await outputService.findAll(data);
return response;
}
@Get('/:id')
async findOne(@Body() data, @Param() params) {
const { id } = params;
console.log(process.env.DEV_URL + `/output/${id}`, 'ON Find One ROUTE');
const outputService = new OutputsService(this.outputsClientService);
const response = await outputService.findOne({ id, ...data });
return response;
}
@Put(':id')
async update(@Payload() updateOutputDto, @Param() params) {
const { id } = params;
const { info } = updateOutputDto;
delete updateOutputDto.info;
console.log(process.env.DEV_URL + `/output/${id}`, 'ON UPDATE ROUTE');
const outputService = new OutputsService(this.outputsClientService);
const response = await outputService.update(id, updateOutputDto, info);
return response;
}
@Delete(':id')
async delete(@Body() data, @Param() params) {
const { id } = params;
console.log(process.env.DEV_URL + `/output/${id}`, 'ON DELETE ROUTE');
const outputService = new OutputsService(this.outputsClientService);
const response = await outputService.remove({ id, ...data });
return response;
}
}
@@ -1,5 +0,0 @@
describe('PipelinesGrpcServerService', () => {
it('should be defined', () => {
expect(2 + 2).toBe(4);
});
});
-63
View File
@@ -1,63 +0,0 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { IIdRequest, Info } from 'src/clients/outputs/interfaces';
import { OutputsClientService } from 'src/clients/outputs/client.service';
@Injectable()
export class OutputsService {
constructor(private outputClient: OutputsClientService) {}
async create(createOutputDto) {
try {
const createOutputResponse = await this.outputClient.create(
createOutputDto,
);
return createOutputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
async findOne(data: IIdRequest) {
try {
const findOneOutputResponse = await this.outputClient.findOne(data);
return findOneOutputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
async findAll(data) {
try {
const findAllOutputResponse = await this.outputClient.findAll(data);
return findAllOutputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
async update(id: string, data, info: Info) {
try {
const updateOutputResponse = await this.outputClient.update({
id,
info,
...data,
});
return updateOutputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
async remove(data: IIdRequest) {
try {
const removeOutputResponse = await this.outputClient.remove(data);
return removeOutputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import { ApiProperty } from '@nestjs/swagger';
export class PermissionGroupPermission {
@ApiProperty()
name: string;
@ApiProperty()
id: number;
}
export class PermissionGroup {
@ApiProperty()
title: string;
@ApiProperty({ type: [PermissionGroupPermission] })
permissions: PermissionGroupPermission[];
}
export class Permission {
@ApiProperty()
id: string;
@ApiProperty()
seqid: number;
@ApiProperty()
name: string;
@ApiProperty()
claim: string;
@ApiProperty()
createdAt: string;
@ApiProperty()
updatedAt: string;
}
export class PermissionDto {
@ApiProperty()
id: number;
@ApiProperty()
seqid: number;
@ApiProperty()
name: string;
@ApiProperty()
claim: string;
@ApiProperty()
createdAt: string;
@ApiProperty()
updatedAt: string;
}
export class GetPublicPermissionsRes {
@ApiProperty({ type: [PermissionGroup] })
permissionGroups: PermissionGroup[];
}

Some files were not shown because too many files have changed in this diff Show More