Compare commits

...
132 Commits
Author SHA1 Message Date
Gabriel Rosa d6b3e02890 FIX: automattically skip ssl when microservices connection are local 2023-02-13 10:30:50 -03:00
Gabriel Amorim d06910515b FIX: force deploy 2023-02-10 15:53:10 -03:00
Gabriel Amorim d1b328d481 Merge pull request #204 from dadosfera/feat/oauth-login
Feat/oauth login
2023-02-10 14:58:20 -03:00
Gabriel Rosa 945955a71c Merge branch 'main' into feat/oauth-login 2023-02-09 15:21:05 -03:00
Gabriel Amorim cdba41dffa Merge pull request #203 from dadosfera/force-deploy
FIX: force deploy
2023-02-03 17:24:13 -03:00
Gabriel Rosa 5020e8913e FIX: force deploy 2023-02-03 17:21:53 -03:00
Gabriel Amorim 89369270b9 Merge pull request #202 from dadosfera/feat/embed-private
Feat/embed private
2023-02-03 17:14:14 -03:00
Gabriel Rosa 52f17725af new protospack version 2023-02-03 15:03:51 -03:00
Gabriel Rosa 6da213aebc starting module strategy 2023-02-03 11:14:15 -03:00
Gabriel Rosa a353a1b45c FIX: new permission 2023-02-02 14:00:24 -03:00
Gabriel Rosa b36ff624b5 FIX: add logic for fetching shared data_assets 2023-02-01 18:00:36 -03:00
Gabriel Rosa 03c09a525b DOCS: documenting oauth flow 2023-02-01 10:07:45 -03:00
Gabriel Amorim 9ea610a405 Merge pull request #201 from dadosfera/fix/oauth-facebook
FIX: add all scopes to facebook oauth
2023-01-31 16:08:15 -03:00
Gabriel Rosa cd97bcbac0 FIX: add all scopes to facebook oauth 2023-01-31 16:04:57 -03:00
Gabriel Rosa 52bf2bdef3 teste new docsfera cloud function url 2023-01-31 09:34:34 -03:00
Gabriel Rosa 161d1fa05d Added some logs 2023-01-30 16:35:05 -03:00
Gabriel Rosa 642bf462ad FIX: add fallback language to pt-br 2023-01-26 17:00:27 -03:00
Gabriel Rosa 6d61d74d0c FIX: set google callback 2023-01-25 09:51:22 -03:00
Gabriel Rosa 2bc060b13d FEAT: oauth sign in 2023-01-24 19:19:00 -03:00
Anderson Feitosa 28efdf95d9 Merge pull request #200 from dadosfera/feature/upload-to-s3-presigned
feature/upload-to-s3-presigned
2023-01-24 06:39:49 -03:00
Anderson Feitosa 1ef9fd4298 FEAT: update packages 2023-01-23 13:56:51 -03:00
Anderson Feitosa 5c24ec5560 FEAT: fix upload id 2023-01-20 10:05:36 -03:00
Anderson Feitosa 962e094a65 FEAT: add upload init multi part upload 2023-01-19 17:17:33 -03:00
Anderson Feitosa 131b166d8d REFACTOR: get data from body 2023-01-16 10:56:03 -03:00
Anderson Feitosa 5419f3690c FEAT: update endpoint to get signed url to upload csv file 2023-01-16 10:55:27 -03:00
Gabriel Amorim 6f3eb7965b Merge pull request #199 from dadosfera/fix/can-untoggle
FIX: canUntoggle logic
2023-01-12 18:10:23 -03:00
Gabriel Rosa 909f0d4a4e new protospack version 2023-01-12 17:59:02 -03:00
Gabriel Rosa 8c8fdf5f88 FIX: canUntoggle logic 2023-01-12 16:47:43 -03:00
Anderson Feitosa e9582a51c2 Merge pull request #198 from dadosfera/feature/mixpanel-first-name
feature/mixpanel-first-name
2023-01-12 12:28:24 -03:00
Anderson Feitosa 0590c20af8 FEAT: add more prefixed values 2023-01-12 12:03:04 -03:00
Anderson Feitosa 05eefe866b FEAT: add suffix on email 2023-01-12 12:02:16 -03:00
Anderson Feitosa fa023756c7 FEAT: replace dadosfera with empty string 2023-01-12 11:56:47 -03:00
Anderson Feitosa 7f967e5e75 FEAT: update hardcoded separator 2023-01-12 11:52:51 -03:00
Anderson Feitosa 2a0b17e45a FEAT: add more prefixed values 2023-01-12 11:49:32 -03:00
Anderson Feitosa b1f6d765b2 FEAT: add more prefixed values 2023-01-12 11:47:04 -03:00
Anderson Feitosa 3c9ed08515 FEAT: split email into fist name and last name 2023-01-12 11:40:14 -03:00
Gabriel Amorim c7ca8297c9 Merge pull request #197 from dadosfera/fix/reset-password-email
FIX: send language on reset password email
2023-01-10 20:29:58 -03:00
Gabriel Rosa d269e9efe4 FIX: send language on reset password email 2023-01-10 18:50:32 -03:00
Gabriel Amorim 61cc238891 Merge pull request #196 from dadosfera/feat/cognito-mailing
FIX: send language on create users and resend invite
2023-01-10 18:06:06 -03:00
Gabriel Rosa b5df916511 Merge branch 'main' into feat/cognito-mailing 2023-01-10 15:05:18 -03:00
Gabriel Amorim 95da106fb8 Merge pull request #195 from dadosfera/fix/customer-schedule-limit
fix/customer-schedule-limit
2023-01-06 18:29:57 -03:00
Gabriel Rosa 5730402312 update protospack 2023-01-06 18:04:16 -03:00
Gabriel Rosa c2ea8b69da FIX: customer scheduleLimit on login 2023-01-06 15:24:01 -03:00
Gabriel Rosa 14081bb9ca FIX: send language on create users and resend invite 2023-01-06 15:18:49 -03:00
Victor Radael 82d9024f24 Merge pull request #193 from dadosfera/feat/update-connection
Feat/update connection
2022-12-27 17:21:25 -03:00
Gabriel Rosa 53a6d3957a Merge branch 'main' into feat/update-connection 2022-12-27 17:01:32 -03:00
Anderson Feitosa 7dab555009 Merge pull request #194 from dadosfera/feature/download-logs
feature/download-logs
2022-12-27 16:41:11 -03:00
Anderson Feitosa 27dacf1257 FEAT: update protospack version 2022-12-27 12:24:15 -03:00
Anderson Feitosa f71bb523ff FEAT: add download logs api route 2022-12-27 12:23:03 -03:00
Victor Radael db55a8abde FEAT: Update OAuth Connection 2022-12-26 17:47:30 -03:00
Victor Radael 8f40b159d6 FEAT: MERGE main to deploy 2022-12-26 17:43:31 -03:00
Anderson Feitosa 5c6f36a760 Merge pull request #192 from dadosfera/feature/pipeline-object-to-data-asset
feature/pipeline-object-to-data-asset
2022-12-23 14:07:33 -03:00
Anderson Feitosa db4a2d9d2e FEAT: update protospack version 2022-12-22 17:43:54 -03:00
Anderson Feitosa 9a1b3e0bf4 FEAT: update protospack 2022-12-22 17:38:58 -03:00
Gabriel Rosa 67d9b3a06e Send metabaseUserId undefined to user update 2022-12-22 17:14:51 -03:00
Anderson Feitosa 1150d21763 FEAT: remove metabase user id 2022-12-22 17:02:03 -03:00
Anderson Feitosa b38e9f26c3 FEAT: add route to get data asset by pipeline and object 2022-12-22 17:00:56 -03:00
Anderson Feitosa 5a23923e53 DOCS: docsfera 2022-12-22 13:16:56 -03:00
Anderson Feitosa 3db7e99d9e FEAT: parse objects 2022-12-22 13:16:44 -03:00
Anderson Feitosa c85cd37f3a FEAT: add new api route to get pipeline objects 2022-12-22 13:08:08 -03:00
Victor Radael 190a63f0eb FEAT: Update OAuth Connection 2022-12-20 17:01:00 -03:00
Gabriel Amorim 0fc8c427a2 Merge pull request #191 from dadosfera/fix/refresh-token
Fix/refresh token
2022-12-16 09:40:56 -03:00
Gabriel Rosa 59c3ce7ed9 FIX: do not use User() on non-authenticated routes 2022-12-16 08:09:20 -03:00
Victor Radael c5c428d3d4 FIX: Refresh token 2022-12-15 19:57:52 -03:00
Victor Radael cda7f9a9b5 FEAT: Edit connection 2022-12-15 16:01:24 -03:00
Victor Radael bb3b71d5f0 Merge remote-tracking branch 'origin' into feat/upload-csv 2022-12-15 16:01:02 -03:00
Victor Radael e47459eb9a FEAT: Edit connection 2022-12-15 16:00:54 -03:00
Gabriel Amorim a2de729e0e Merge pull request #190 from dadosfera/feat/cognito-app-client
Feat/cognito app client
2022-12-14 13:20:29 -03:00
Gabriel Rosa e2cb02e51a new protospack version 2022-12-14 10:49:33 -03:00
Gabriel Rosa 8a0c91eca5 new protospack version 2022-12-14 10:02:02 -03:00
Gabriel Rosa 15302d80bc Merge branch 'main' into feat/cognito-app-client 2022-12-13 16:12:19 -03:00
Gabriel Rosa 5aa63154c5 new protospack version 2022-12-13 15:34:57 -03:00
Gabriel Rosa 48af897e79 FEAT: send customer_name on refresh token 2022-12-13 09:28:42 -03:00
Victor Radael 9eb61a0ac9 Merge pull request #189 from dadosfera/feat/upload-csv
FIX: Upload CSV
2022-12-12 17:41:44 -03:00
Victor Radael 4a9e5b0ffc FIX: Upload CSV 2022-12-12 17:04:16 -03:00
Victor Radael 23fe522cc1 Merge pull request #188 from dadosfera/feat/upload-csv
Feat/upload csv
2022-12-09 17:14:36 -03:00
Victor Radael ea8dfa2e12 FEAT: Deploy Merge 2022-12-09 17:10:50 -03:00
Anderson Feitosa 9477223bec Merge pull request #187 from dadosfera/hotfix/semantic-release
hotfix/semantic-release
2022-12-09 11:32:54 -03:00
Anderson Feitosa 519070bd82 FIX: change docs 2022-12-09 11:32:23 -03:00
Anderson Feitosa 99fc7e8a96 Merge pull request #186 from dadosfera/feature/verify-refresh-token
feature/verify-refresh-token
2022-12-09 11:25:44 -03:00
Anderson Feitosa 9fedd4bb80 Merge branch 'main' into feature/verify-refresh-token 2022-12-09 11:19:45 -03:00
Victor Radael ff5e735121 FEAT: Find a file pipeline 2022-12-09 08:29:27 -03:00
Gabriel Amorim c450d16445 Merge pull request #184 from dadosfera/feat/embed-data-asset
Feat/embed data asset
2022-12-07 16:26:49 -03:00
Gabriel Rosa 3acda53e8a npm audit fix 2022-12-07 16:17:21 -03:00
Gabriel Rosa a0f21b763a new protospack version 2022-12-07 16:14:57 -03:00
Gabriel Amorim c83d8ab1b2 Merge branch 'main' into feat/embed-data-asset 2022-12-07 15:48:04 -03:00
Gabriel Amorim 610d08fa11 Merge pull request #185 from dadosfera/fix/data-asset-docs
FIX: removed split on id on catalog POST docs route
2022-12-07 14:55:49 -03:00
Gabriel Rosa 21f68b466d FIX: removed split on id on catalog POST docs route 2022-12-07 14:49:58 -03:00
Anderson Feitosa 18b5dc980c FEAT: add verify access token path 2022-12-07 12:58:58 -03:00
Gabriel Rosa 6fdd73c58c removing treatment of undefined embed field 2022-12-07 08:36:21 -03:00
Victor Radael eaddca7eb3 FEAT: Creat a Upload CSV Pipeline 2022-12-06 18:06:32 -03:00
Gabriel Rosa 1c0377a4fa FIX: treat empty embed on update 2022-12-06 11:04:18 -03:00
Gabriel Rosa 64816e5578 FIX: treat empty embed 2022-12-06 10:07:30 -03:00
Gabriel Rosa f9064417ce docsfera.json 2022-12-06 09:11:32 -03:00
Gabriel Rosa 0b8bf9a535 Merge branch 'main' into feat/embed-data-asset 2022-12-06 09:09:43 -03:00
Victor Radael 98e865c48a FEAT: Create Upload CSV Pipeline 2022-12-05 20:35:35 -03:00
Victor Radael 2c207f068c FEAT: Create Upload CSV Pipeline 2022-12-05 20:34:04 -03:00
Gabriel Amorim 6f1e5d08c9 Merge pull request #182 from dadosfera/feat/view-table
Feat/view table
2022-12-05 18:15:05 -03:00
Gabriel Rosa b611c1eb9d Merge branch 'main' into feat/view-table 2022-12-05 18:13:36 -03:00
Gabriel Amorim d7062efc60 Merge pull request #183 from dadosfera/feat/rest-api
Feat/rest api
2022-12-05 15:22:05 -03:00
Gabriel Rosa 4a3dd98977 Merge branch 'main' into feat/rest-api 2022-12-05 13:58:22 -03:00
Gabriel Rosa 81d93ff103 FEAT: embed data asset 2022-12-05 13:22:23 -03:00
Victor Radael ea7ec52e1f FEAT: upload 50MB file 2022-12-01 18:55:48 -03:00
Victor Radael be228b7425 FEAT: upload 50MB file 2022-12-01 17:50:44 -03:00
Victor Radael 90edc6552a FEAT: Upload CSV 2022-12-01 14:57:44 -03:00
Victor Radael ed96609011 FEAT: Upload CSV 2022-12-01 14:56:41 -03:00
Gabriel Rosa d55563f62f FIX: removed split on data_asset id on DELETE route 2022-11-30 16:21:36 -03:00
Gabriel Rosa ba9575c53e FIX: return only owner username 2022-11-30 15:43:30 -03:00
Gabriel Rosa d88b154d43 FIX: new id strategy for data assets 2022-11-30 15:29:31 -03:00
Gabriel Amorim 06f4260b61 Merge pull request #181 from dadosfera/stg2
Stg2
2022-11-29 19:16:48 -03:00
Gabriel Rosa 64717c8d92 Merge branch 'main' into stg2 2022-11-29 14:41:11 -03:00
Anderson Feitosa e6e66c77e1 Merge pull request #180 from dadosfera/feature/update-pipeline-cron
feature/update-pipeline-cron
2022-11-29 14:04:31 -03:00
Gabriel Rosa 131e82b4c5 FIX: connection details improvements 2022-11-29 09:47:19 -03:00
Gabriel Rosa 32d37ecadf Removed dev deploy environment 2022-11-29 08:42:26 -03:00
Gabriel Rosa 4d667acb86 removed unused import 2022-11-25 18:21:58 -03:00
Gabriel Rosa 9d417ff61b FIX: treating new connectors upload reponse 2022-11-25 18:21:43 -03:00
Anderson Feitosa b01c4c6c25 UPGRADE: change protospack version 2022-11-25 13:00:09 -03:00
Anderson Feitosa 449a1abb63 FEAT: update auth guard to patch method 2022-11-25 12:11:51 -03:00
Anderson Feitosa 04ca17a69d FEAT: update protospack 2022-11-25 11:37:34 -03:00
Anderson Feitosa 34a0eb40a7 DOCS: docsfera patch 2022-11-23 14:58:17 -03:00
Anderson Feitosa 2df69fba84 FEAT: add log on update 2022-11-23 13:35:02 -03:00
Anderson Feitosa dcbcc5ff36 FEAT: add patch method on pipeline controller 2022-11-23 13:02:32 -03:00
Gabriel Rosa 703b5f36b3 Merge branch 'main' into feat/rest-api 2022-11-21 13:36:13 -03:00
Gabriel Rosa 54d10cb873 FIX: try to delete pipeline even if it does not exists in opensearch 2022-11-17 09:25:05 -03:00
Gabriel Rosa 3c877b8b8d Merge branch 'main' into feat/rest-api 2022-11-10 15:14:45 -03:00
Gabriel Rosa e6b0034805 Merge branch 'main' into feat/rest-api 2022-11-09 18:29:04 -03:00
Gabriel Rosa b9c1f784d3 FIX: treatingconnector upload response 2022-11-09 18:25:57 -03:00
Gabriel Rosa 3edf5c2416 Merge branch 'main' into feat/rest-api 2022-11-09 15:03:09 -03:00
Gabriel Rosa f3e00a15c8 Remove unused imports 2022-11-09 14:06:30 -03:00
Gabriel Rosa 0422c7a369 Set branch name on swagger version 2022-11-09 14:06:12 -03:00
Gabriel Rosa f22d9370db test new upload documentation 2022-11-09 11:27:54 -03:00
Gabriel Rosa a9ee74724f FIX: better error handling 2022-11-08 12:11:05 -03:00
48 changed files with 1596 additions and 768 deletions
-1
View File
@@ -10,7 +10,6 @@ on:
required: true
type: choice
options:
- dev
- stg
- stg2
- prd
+1 -1
View File
@@ -290,7 +290,7 @@ Example: `FIX: ensure Range headers adhere more closely to RFC 2616`
### 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
3. Send Pull Request link in Microsfera Google Chat Group for review and possible approval
## 🛠️ Built with
Some technologies used in this project:
+409 -28
View File
@@ -79,6 +79,16 @@
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AuthRefreshAccessTokenReq"
}
}
}
},
"responses": {
"200": {
"description": "",
@@ -264,6 +274,85 @@
]
}
},
"/auth/verify-access-token": {
"get": {
"operationId": "AuthController_verifyAccessToken",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
"/auth/session/{session}": {
"get": {
"operationId": "AuthController_getSession",
"parameters": [
{
"name": "session",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Auth"
]
}
},
"/auth/oauth/google": {
"get": {
"operationId": "AuthController_googleOauth",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"Auth"
]
}
},
"/auth/oauth/google/callback": {
"get": {
"operationId": "AuthController_googleOauthCallback",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
"/connectors": {
"post": {
"operationId": "ConnectorController_uploadConnector",
@@ -505,18 +594,16 @@
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeleteConnectorDto"
}
},
{
"name": "version",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
},
],
"responses": {
"200": {
"description": ""
@@ -948,7 +1035,7 @@
],
"responses": {
"200": {
"description": "List of users of a customer",
"description": "",
"content": {
"application/json": {
"schema": {
@@ -1565,6 +1652,17 @@
],
"type": "string"
}
},
{
"name": "canUntoggleLogic",
"required": false,
"in": "query",
"schema": {
"enum": [
"data_assets_sharing"
],
"type": "string"
}
}
],
"responses": {
@@ -2298,6 +2396,20 @@
]
}
},
"/pipelinesV2/download-logs": {
"get": {
"operationId": "PipelinesController_downloadLogs",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"PipelinesV2"
]
}
},
"/pipelinesV2/{id}/config": {
"get": {
"operationId": "PipelinesController_getPipelineproperties",
@@ -2312,6 +2424,20 @@
]
}
},
"/pipelinesV2/{id}/objects": {
"get": {
"operationId": "PipelinesController_getPipelineObjects",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"PipelinesV2"
]
}
},
"/pipelinesV2/{id}": {
"get": {
"operationId": "PipelinesController_findOne",
@@ -2351,6 +2477,25 @@
"PipelinesV2"
]
},
"patch": {
"operationId": "PipelinesController_updateByPatch",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"PipelinesV2"
]
},
"delete": {
"operationId": "PipelinesController_delete",
"parameters": [],
@@ -2364,6 +2509,92 @@
]
}
},
"/pipelinesV2/init-upload": {
"post": {
"operationId": "PipelinesController_initUploadFile",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IInitUploadCSVFile"
}
}
}
},
"responses": {
"201": {
"description": ""
}
},
"tags": [
"PipelinesV2"
]
}
},
"/pipelinesV2/complete-upload": {
"post": {
"operationId": "PipelinesController_completeUploadFile",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ICompleteUploadCSVFile"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"PipelinesV2"
]
}
},
"/pipelinesV2/file": {
"post": {
"operationId": "PipelinesController_uploadedFile",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ICreatePipelineCSVFile"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"PipelinesV2"
]
}
},
"/inputs/available-entities/{plugin}": {
"get": {
"operationId": "InputsController_getAvailableEntities",
@@ -2609,7 +2840,7 @@
},
"/catalog": {
"get": {
"operationId": "CatalogController_catalogAll",
"operationId": "CatalogController_searchCatalog",
"parameters": [],
"responses": {
"200": {
@@ -2650,6 +2881,20 @@
]
}
},
"/catalog/data-asset": {
"get": {
"operationId": "CatalogController_findByPipelineAndObject",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Catalog"
]
}
},
"/catalog/tags": {
"get": {
"operationId": "CatalogController_findAllTags",
@@ -2674,7 +2919,16 @@
"/catalog/data-asset/{id}": {
"get": {
"operationId": "CatalogController_getDataAsset",
"parameters": [],
"parameters": [
{
"name": "shared",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
@@ -2820,7 +3074,16 @@
},
"post": {
"operationId": "CatalogController_manageDataAssetDocs",
"parameters": [],
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"201": {
"description": "",
@@ -3216,9 +3479,9 @@
}
},
"info": {
"title": "Maestro - feat/incremental-table",
"title": "Maestro - main",
"description": "Documentation for Maestro gateway",
"version": "16.11.2022 - 16:46",
"version": "1.0.0",
"contact": {}
},
"tags": [],
@@ -3267,13 +3530,17 @@
},
"tier": {
"type": "string"
},
"scheduleLimit": {
"type": "string"
}
},
"required": [
"modules",
"id",
"name",
"tier"
"tier",
"scheduleLimit"
]
},
"AuthUser": {
@@ -3402,6 +3669,21 @@
"mfaStatus"
]
},
"AuthRefreshAccessTokenReq": {
"type": "object",
"properties": {
"refreshToken": {
"type": "string"
},
"customerName": {
"type": "string"
}
},
"required": [
"refreshToken",
"customerName"
]
},
"CreateConnectorDto": {
"type": "object",
"properties": {
@@ -3486,17 +3768,6 @@
"tags"
]
},
"DeleteConnectorDto": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": [
"version"
]
},
"PermissionGroupPermission": {
"type": "object",
"properties": {
@@ -3862,6 +4133,10 @@
},
"usage": {
"type": "string"
},
"canUntoggle": {
"type": "boolean",
"description": "Whether this role can be untoggled"
}
},
"required": [
@@ -4339,6 +4614,10 @@
},
"usage": {
"type": "string"
},
"canUntoggle": {
"type": "boolean",
"description": "Whether this role can be untoggled"
}
},
"required": [
@@ -4429,6 +4708,10 @@
},
"usage": {
"type": "string"
},
"canUntoggle": {
"type": "boolean",
"description": "Whether this role can be untoggled"
}
},
"required": [
@@ -5092,6 +5375,74 @@
"updated_at"
]
},
"IInitUploadCSVFile": {
"type": "object",
"properties": {
"file_name": {
"type": "string"
},
"parts": {
"type": "number"
}
},
"required": [
"file_name",
"parts"
]
},
"ICompleteUploadCSVFile": {
"type": "object",
"properties": {
"upload_id": {
"type": "string"
},
"file_name": {
"type": "string"
},
"parts": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"upload_id",
"file_name",
"parts"
]
},
"ICreatePipelineCSVFile": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"file_name": {
"type": "string"
},
"description": {
"type": "string"
},
"encoding": {
"type": "string"
},
"sep": {
"type": "string"
},
"header": {
"type": "boolean"
}
},
"required": [
"name",
"file_name",
"description",
"encoding",
"sep",
"header"
]
},
"AvailableEntity": {
"type": "object",
"properties": {
@@ -5276,6 +5627,17 @@
"username"
]
},
"EmbedObject": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
},
"required": [
"url"
]
},
"IDataAsset": {
"type": "object",
"properties": {
@@ -5374,10 +5736,14 @@
"items": {
"$ref": "#/components/schemas/IComment"
}
},
"embed": {
"$ref": "#/components/schemas/EmbedObject"
}
},
"required": [
"id",
"display_name",
"pipeline_id",
"user_id",
"description",
@@ -5386,6 +5752,7 @@
"owner",
"roles",
"users",
"created_at",
"comments"
]
},
@@ -5615,6 +5982,17 @@
"items": {
"type": "string"
}
},
"embed": {
"$ref": "#/components/schemas/EmbedObject"
},
"share_type": {
"type": "string",
"enum": [
"none",
"public",
"private"
]
}
},
"required": [
@@ -5651,6 +6029,9 @@
},
"location": {
"type": "string"
},
"embed": {
"$ref": "#/components/schemas/EmbedObject"
}
},
"required": [
+73 -149
View File
@@ -12,7 +12,7 @@
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack-v2": "3.20.0",
"@dadosfera/protospack-v2": "3.29.0",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/common": "^8.4.7",
@@ -31,12 +31,12 @@
"dotenv": "^14.3.2",
"elastic-apm-node": "^3.36.0",
"helmet": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.0",
"jwk-to-pem": "^2.0.5",
"mixpanel": "^0.17.0",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-forcedotcom": "^0.1.5",
"passport-forcedotcom": "^0.2.0",
"passport-google-oauth20": "^2.0.0",
"passport-hubspot-oauth2": "^1.0.3",
"passport-mailchimp": "^1.1.0",
@@ -1727,9 +1727,9 @@
}
},
"node_modules/@dadosfera/protospack-v2": {
"version": "3.20.0",
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.20.0.tgz",
"integrity": "sha512-eatmjfYmfD2Rqxi3L6/XDOqkImYc9PvChTTXO03RgiOk2IDEALvacPGp6j1bce2jfNMA2LoP3Bd1ugklecDPgg==",
"version": "3.29.0",
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.29.0.tgz",
"integrity": "sha512-G66u9V+/+5rQb+fpJdDXydTaji2Mlr0Ro5TTYcQleITKU9DjF1WZs/jP/Qu0Y224nCb8HhePvaVl/Rr0wtLt4w==",
"dependencies": {
"@grpc/grpc-js": "^1.6.7",
"rxjs": "^7.5.5",
@@ -3699,8 +3699,9 @@
},
"node_modules/asap": {
"version": "2.0.6",
"dev": true,
"license": "MIT"
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true
},
"node_modules/asn1.js": {
"version": "5.4.1",
@@ -4705,9 +4706,10 @@
}
},
"node_modules/dezalgo": {
"version": "1.0.3",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true,
"license": "ISC",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
@@ -5902,23 +5904,28 @@
}
},
"node_modules/formidable": {
"version": "2.0.1",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"dezalgo": "1.0.3",
"hexoid": "1.0.0",
"once": "1.4.0",
"qs": "6.9.3"
"dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
"once": "^1.4.0",
"qs": "^6.11.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/formidable/node_modules/qs": {
"version": "6.9.3",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
@@ -7779,30 +7786,18 @@
}
},
"node_modules/jsonwebtoken": {
"version": "8.5.1",
"license": "MIT",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"lodash": "^4.17.21",
"ms": "^2.1.1",
"semver": "^5.6.0"
"semver": "^7.3.8"
},
"engines": {
"node": ">=4",
"npm": ">=1.4.28"
}
},
"node_modules/jsonwebtoken/node_modules/semver": {
"version": "5.7.1",
"license": "ISC",
"bin": {
"semver": "bin/semver"
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
@@ -7901,30 +7896,6 @@
"version": "4.3.0",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"license": "MIT"
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"dev": true,
@@ -7935,10 +7906,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"license": "MIT"
},
"node_modules/lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -8713,24 +8680,11 @@
}
},
"node_modules/passport-forcedotcom": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/passport-forcedotcom/-/passport-forcedotcom-0.1.5.tgz",
"integrity": "sha512-raDRQqBJtTEHDxLkxLG+9sYE0xpPy9PqriMPbMy9jmpIcU2+mOq9PgczvUPrU8UjfcZp3gqkDHqujcUWiGOVNQ==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/passport-forcedotcom/-/passport-forcedotcom-0.2.0.tgz",
"integrity": "sha512-gRI5+zQK9iVAZ1hJgt3TkWLeDw8SYrNWUsmVk0d0RyvUviQ4mT52x3JbYRiyZefoeID8afmLsJH8VOHjWdPf3Q==",
"dependencies": {
"passport-oauth2": "1.3.x"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/passport-forcedotcom/node_modules/passport-oauth2": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.3.0.tgz",
"integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==",
"dependencies": {
"oauth": "0.9.x",
"passport-strategy": "1.x.x",
"uid2": "0.0.x"
"passport-oauth2": "^1.6.1"
},
"engines": {
"node": ">= 0.4.0"
@@ -9490,9 +9444,9 @@
"license": "MIT"
},
"node_modules/semver": {
"version": "7.3.7",
"dev": true,
"license": "ISC",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -12330,9 +12284,9 @@
}
},
"@dadosfera/protospack-v2": {
"version": "3.20.0",
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.20.0.tgz",
"integrity": "sha512-eatmjfYmfD2Rqxi3L6/XDOqkImYc9PvChTTXO03RgiOk2IDEALvacPGp6j1bce2jfNMA2LoP3Bd1ugklecDPgg==",
"version": "3.29.0",
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.29.0.tgz",
"integrity": "sha512-G66u9V+/+5rQb+fpJdDXydTaji2Mlr0Ro5TTYcQleITKU9DjF1WZs/jP/Qu0Y224nCb8HhePvaVl/Rr0wtLt4w==",
"requires": {
"@grpc/grpc-js": "^1.6.7",
"rxjs": "^7.5.5",
@@ -13675,6 +13629,8 @@
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true
},
"asn1.js": {
@@ -14372,7 +14328,9 @@
"dev": true
},
"dezalgo": {
"version": "1.0.3",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true,
"requires": {
"asap": "^2.0.0",
@@ -15214,18 +15172,25 @@
}
},
"formidable": {
"version": "2.0.1",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dev": true,
"requires": {
"dezalgo": "1.0.3",
"hexoid": "1.0.0",
"once": "1.4.0",
"qs": "6.9.3"
"dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
"once": "^1.4.0",
"qs": "^6.11.0"
},
"dependencies": {
"qs": {
"version": "6.9.3",
"dev": true
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dev": true,
"requires": {
"side-channel": "^1.0.4"
}
}
}
},
@@ -16450,23 +16415,14 @@
}
},
"jsonwebtoken": {
"version": "8.5.1",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"lodash": "^4.17.21",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"dependencies": {
"semver": {
"version": "5.7.1"
}
"semver": "^7.3.8"
}
},
"jwa": {
@@ -16539,24 +16495,6 @@
"lodash.camelcase": {
"version": "4.3.0"
},
"lodash.includes": {
"version": "4.3.0"
},
"lodash.isboolean": {
"version": "3.0.3"
},
"lodash.isinteger": {
"version": "4.0.4"
},
"lodash.isnumber": {
"version": "3.0.3"
},
"lodash.isplainobject": {
"version": "4.0.6"
},
"lodash.isstring": {
"version": "4.0.1"
},
"lodash.memoize": {
"version": "4.1.2",
"dev": true
@@ -16565,9 +16503,6 @@
"version": "4.6.2",
"dev": true
},
"lodash.once": {
"version": "4.1.1"
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -17093,23 +17028,11 @@
}
},
"passport-forcedotcom": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/passport-forcedotcom/-/passport-forcedotcom-0.1.5.tgz",
"integrity": "sha512-raDRQqBJtTEHDxLkxLG+9sYE0xpPy9PqriMPbMy9jmpIcU2+mOq9PgczvUPrU8UjfcZp3gqkDHqujcUWiGOVNQ==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/passport-forcedotcom/-/passport-forcedotcom-0.2.0.tgz",
"integrity": "sha512-gRI5+zQK9iVAZ1hJgt3TkWLeDw8SYrNWUsmVk0d0RyvUviQ4mT52x3JbYRiyZefoeID8afmLsJH8VOHjWdPf3Q==",
"requires": {
"passport-oauth2": "1.3.x"
},
"dependencies": {
"passport-oauth2": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.3.0.tgz",
"integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==",
"requires": {
"oauth": "0.9.x",
"passport-strategy": "1.x.x",
"uid2": "0.0.x"
}
}
"passport-oauth2": "^1.6.1"
}
},
"passport-google-oauth20": {
@@ -17591,8 +17514,9 @@
}
},
"semver": {
"version": "7.3.7",
"dev": true,
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"requires": {
"lru-cache": "^6.0.0"
}
+3 -3
View File
@@ -28,7 +28,7 @@
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack-v2": "3.20.0",
"@dadosfera/protospack-v2": "3.29.0",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/common": "^8.4.7",
@@ -47,12 +47,12 @@
"dotenv": "^14.3.2",
"elastic-apm-node": "^3.36.0",
"helmet": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.0",
"jwk-to-pem": "^2.0.5",
"mixpanel": "^0.17.0",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-forcedotcom": "^0.1.5",
"passport-forcedotcom": "^0.2.0",
"passport-google-oauth20": "^2.0.0",
"passport-hubspot-oauth2": "^1.0.3",
"passport-mailchimp": "^1.1.0",
+43 -1
View File
@@ -336,6 +336,16 @@ export const PERMISSIONS_GROUPS = {
'es-es': 'Gestor de catálogos. Puede ver y editar todos los activos.',
},
},
EMBED_ANALYTICS: {
seqid: 44,
claim: 'catalog:embed',
usage: PermissionUsages.INTERNAL,
name: {
'pt-br': 'Acessar Módulo de Incorporação de Ativos',
'en-us': 'Access Embedding analytics Module',
'es-es': 'Acceder al Módulo de Incorporación de Activos',
},
},
},
},
@@ -556,7 +566,34 @@ export const PERMISSIONS_GROUPS = {
},
},
};
export interface DadosferaModule {
name: string;
description: string;
key: string;
permissionSeqId: number;
}
export const DADOSFERA_MODULES: Array<DadosferaModule> = [
{
name: 'Intelligence Module',
description: 'Orchest Module',
key: 'intelligence',
permissionSeqId: PERMISSIONS_GROUPS.ANALYZE.permissions.INTELLIGENCE.seqid,
},
{
name: 'Proccessing Module',
description: 'Proccessing Module',
key: 'process',
permissionSeqId:
PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid,
},
{
name: 'Embedded Analytics',
description: 'Embedded Analytics Module',
key: 'embedded-analytics',
permissionSeqId:
PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid,
},
];
// traverses the object searching for duplicate seqids or claims (executes at runtime)
let nextAvailableSeqid = 0;
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
@@ -576,4 +613,9 @@ Object.values(PERMISSIONS_GROUPS).map((namespace) =>
}),
);
DADOSFERA_MODULES.map((m) => m.key).forEach((m, i, arr) => {
if (arr.indexOf(m) !== i)
throw new Error(`DADOSFERA_MODULES[${i}] does not have a unique key`);
});
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
+1 -7
View File
@@ -30,12 +30,8 @@ async function bootstrap() {
await app.listen(3333);
}
function configureSwagger(app: INestApplication) {
const date = new Date();
const swaggerVersion = `${date.getDate()}.${
date.getMonth() + 1
}.${date.getFullYear()} - ${date.getHours()}:${date.getMinutes()}`;
function configureSwagger(app: INestApplication) {
let branchName = '';
try {
branchName = execSync('git rev-parse --abbrev-ref HEAD')
@@ -50,7 +46,6 @@ function configureSwagger(app: INestApplication) {
const config = new DocumentBuilder()
.setTitle(swaggerTitle)
.setDescription('Documentation for Maestro gateway')
.setVersion(swaggerVersion)
.addBearerAuth(
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'Authorization',
@@ -60,7 +55,6 @@ function configureSwagger(app: INestApplication) {
const document = SwaggerModule.createDocument(app, config);
if (process.env.ENV === 'local') SwaggerModule.setup('api', app, document);
writeFileSync(
'docsfera.json',
JSON.stringify(
+38
View File
@@ -0,0 +1,38 @@
# Auth
## SSO/oAuth
### Strategy
We are using [PassportJs](https://www.passportjs.org/) to handle oAuth authentications.
When the client (front end) makes a `GET /auth/oauth/{strategy}` Passport automatically redirects the user to the `strategy` login page. To do that we must configure and use a **Passport Strategy**. We must also have a callback route, conventionally `GET /auth/oauth/{strategy}/callback`, so the oAuth app can report the status of the user's login.
- If the oAuth is successfull we call DUC's `AuthOauthSignIn` request that gets the tokens from Cognito and saves them on cache temporarily under a key we call `session`. Duc returns that `session` to maestro which then redirects the user to our app login page with that `session` as a query param.
- If the oAuth login is not successfull for some reason or the user **does not** exist on DUC's database we redirect the user to our login page with an `error` and `error_description` as query params.
### Routes
So in order to have an SSO login, besides configuring the Strategy, we must have two routes for each Strategy, like in the example below:
```ts
@Get('oauth/google')
@UseGuards(AuthGuard('google-login'))
googleOauth() {
this.logger.info('/oauth/google');
return true;
}
@Get('oauth/google/callback')
@UseGuards(AuthGuard('google-login'))
@Redirect()
async googleOauthCallback(@Req() req) {
const { url, email, token, language = 'pt-br' } = await this.callback(req);
if (url.searchParams.get('error')) {
this.logger.error('/oauth/google - ERROR');
return { url: url.href };
}
// ... Rest of the logic
return { url: url.href };
}
```
+145 -11
View File
@@ -7,10 +7,14 @@ import {
HttpStatus,
Inject,
UseFilters,
Get,
UseGuards,
Redirect,
Req,
Param,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
AuthRefreshAccessTokenRequest,
AuthChangePasswordRequest,
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
@@ -21,19 +25,31 @@ import {
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import { RequireAllPermissions } from 'src/authentication/authentication.decorator';
import {
Authenticated,
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';
import { Metadata } from '@grpc/grpc-js';
import { AuthSignInReq, AuthSignInRes } from './dtos/login';
import {
AuthRefreshAccessTokenReq,
AuthSignInReq,
AuthSignInRes,
} from './dtos/login';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
import ErrorCodes, { OauthErrors } from 'src/utils/errorCodes';
import jwt from 'jsonwebtoken';
@ApiTags('Auth')
@UseFilters(new GrpcToHttpExceptionFilter())
@Controller('auth')
export class AuthController {
logger: DadosferaLogger;
redirectUrl: string;
constructor(
@Inject(DadosferaLogger)
@@ -41,6 +57,17 @@ export class AuthController {
private authClient: AuthClientService,
) {
this.logger = dadosferaLogger.logger;
switch (process.env.ENV) {
case 'stg':
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/auth/login`;
break;
case 'prd':
this.redirectUrl = `https://app.dadosfera.ai/auth/login`;
break;
default:
this.redirectUrl = `http://localhost:4200/auth/login`;
}
}
@Post('sign-in')
@@ -50,8 +77,7 @@ export class AuthController {
@Headers('Dadosfera-Lang') language: string,
): Promise<AuthSignInRes> {
this.logger.info('/auth - SignIn');
const metadata = new Metadata();
metadata.add('language', language || 'pt-br');
const metadata = PackTheMetadata({ language: language || 'pt-br' });
return this.authClient.signIn({ username, password, totp }, metadata);
}
@@ -73,12 +99,16 @@ export class AuthController {
@Post('refresh-access-token')
@HttpCode(HttpStatus.OK)
async refreshAccessToken(
@Body() { refreshToken }: AuthRefreshAccessTokenRequest,
@Body()
{ refreshToken, customerName: customer_name }: AuthRefreshAccessTokenReq,
@Headers('Dadosfera-Lang') language: string,
) {
this.logger.info('/auth - RefreshAccessToken');
const metadata = new Metadata();
metadata.add('language', language || 'pt-br');
const metadata = PackTheMetadata({
customer_name,
language,
});
return this.authClient.refreshAccessToken({ refreshToken }, metadata);
}
@@ -103,12 +133,18 @@ export class AuthController {
@Post('reset-password')
@HttpCode(HttpStatus.OK)
async resetPassword(@Body() body: AuthResetPasswordRequest) {
async resetPassword(
@Body() body: AuthResetPasswordRequest,
@Headers('dadosfera-lang') language,
) {
this.logger.info('/auth - reset-password');
const metadata = PackTheMetadata({
language: language,
});
const { username } = body;
return this.authClient.resetPassword({ username });
return this.authClient.resetPassword({ username }, metadata);
}
@Post('verify-reset-password-code')
@@ -185,4 +221,102 @@ export class AuthController {
return this.authClient.verifyTotp({ accessToken, totp });
}
@Authenticated()
@Get('verify-access-token')
verifyAccessToken() {
return { access_token_status: 'valid' };
}
@Get('session/:session')
getSession(@Param('session') session: string) {
return this.authClient.getSession(session);
}
@Get('oauth/google')
@UseGuards(AuthGuard('google-login'))
googleOauth() {
this.logger.info('/oauth/google');
return true;
}
@Get('oauth/google/callback')
@UseGuards(AuthGuard('google-login'))
@Redirect()
async googleOauthCallback(@Req() req) {
const { url, email, token, language = 'pt-br' } = await this.callback(req);
if (url.searchParams.get('error')) {
this.logger.error('/oauth/google - ERROR');
return { url: url.href };
}
await this.authClient
.oauthSignIn({
username: email,
token,
})
.then(({ session }) => {
this.logger.info('/oauth/google - SUCESS');
url.searchParams.set('session', session);
})
.catch((err) => {
this.logger.error('/oauth/google - ERROR');
let error = OauthErrors.INVALID_CREDENTIALS[language].error;
let error_description =
OauthErrors.INVALID_CREDENTIALS[language].error_description;
switch (err.details) {
case ErrorCodes.USER.NOT_FOUND:
error = OauthErrors.USER_NOT_FOUND[language].error;
error_description =
OauthErrors.USER_NOT_FOUND[language].error_description(email);
break;
case ErrorCodes.AUTH.UNAUTHORIZED:
error = OauthErrors.INVALID_SESSION[language].error;
error_description =
OauthErrors.INVALID_SESSION[language].error_description;
break;
}
url.searchParams.set('error', error);
url.searchParams.set('error_description', error_description);
return null;
});
return { url: url.href };
}
async callback(req: Request) {
const { error, state } = req.query;
const { authInfo } = req;
const url = new URL(this.redirectUrl);
let email, token, error_title, error_description;
let language: 'pt-br' | 'en-us' = 'pt-br';
const stateObject = jwt.verify(
state as string,
process.env.JWT_PRIVATE_KEY,
);
if (typeof stateObject != 'string') language = stateObject.language;
if (error || !authInfo) {
this.logger.error(error);
if (!authInfo) this.logger.error('No authInfo', { request: req });
error_title = OauthErrors.INVALID_CREDENTIALS[language].error;
error_description =
OauthErrors.INVALID_CREDENTIALS[language].error_description;
if (error) error_description += ` - [${error}]`;
} else {
const { accessToken } = authInfo as any;
const { _json: userInfo } = req.user as any;
email = userInfo.email;
token = accessToken;
}
if (error_title) {
url.searchParams.set('error', error_title);
url.searchParams.set('error_description', error_description);
}
return { token, email, url, language };
}
}
+8 -1
View File
@@ -6,12 +6,19 @@ import { AuthController } from './auth.controller';
import { AuthClientService } from './auth.service';
import { DucClient } from '../duc/client.config';
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
import { getOauthSecrets } from 'src/utils/OauthSecrets';
const client = new DucClient();
@Module({
imports: [ClientsModule.register([client.providerOptions])],
controllers: [AuthController],
providers: [AuthClientService, DadosferaLogger],
providers: [
AuthClientService,
DadosferaLogger,
GoogleLoginStrategy,
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
],
exports: [AuthClientService],
})
export class AuthModule {}
+16 -2
View File
@@ -90,10 +90,15 @@ export class AuthClientService implements OnModuleInit {
);
}
async resetPassword({ username }: AuthResetPasswordRequest) {
async resetPassword(
{ username }: AuthResetPasswordRequest,
metadata: Metadata,
) {
this.logger.info('resetPassword');
return lastValueFrom(this.authService.AuthResetPassword({ username }));
return lastValueFrom(
this.authService.AuthResetPassword({ username }, metadata),
);
}
async verifyResetPasswordCode({
@@ -152,4 +157,13 @@ export class AuthClientService implements OnModuleInit {
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
);
}
async getSession(session: string) {
return lastValueFrom(this.authService.AuthGetSession({ session }));
}
async oauthSignIn(data: { username: string; token: string }) {
const { username, token } = data;
return lastValueFrom(this.authService.AuthOauthSignIn({ token, username }));
}
}
+9
View File
@@ -72,6 +72,8 @@ export class AuthCustomer {
name: string;
@ApiProperty()
tier: string;
@ApiProperty()
scheduleLimit: string;
}
export class AuthSignInReq implements AuthSignInRequest {
@@ -97,3 +99,10 @@ export class AuthSignInRes implements AuthSignInResponse {
@ApiPropertyOptional()
termsOfUse?: TermsOfUseStatus;
}
export class AuthRefreshAccessTokenReq {
@ApiProperty()
refreshToken: string;
@ApiProperty()
customerName: string;
}
@@ -0,0 +1,46 @@
import {
AuthenticateOptionsGoogle,
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 { Request } from 'express';
import jwt from 'jsonwebtoken';
@Injectable()
export class GoogleLoginStrategy extends PassportStrategy(
Strategy,
'google-login',
) {
redirect_uri: string;
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
const options: StrategyOptions = {
clientID: oauthSecrets['google-login'].client_id,
clientSecret: oauthSecrets['google-login'].client_secret,
callbackURL: oauthSecrets['google-login'].redirect_uri,
scope: ['email', 'profile', 'openid'],
};
const verify = (
accessToken: string,
refreshToken: string,
profile: Profile,
done,
) => {
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
authenticate(req: Request, options: AuthenticateOptionsGoogle) {
const language = req.headers['dadosfera-lang'] || req.query.language;
options.state = jwt.sign({ language }, process.env.JWT_PRIVATE_KEY);
super.authenticate(req, options);
}
}
+3 -4
View File
@@ -6,6 +6,8 @@ import {
import { credentials } from '@grpc/grpc-js';
import { Catalog } from '@dadosfera/protospack-v2';
const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
export class CatalogClientConfiguration {
public name = 'CatalogClientConfiguration';
private config: GrpcOptions = {
@@ -16,10 +18,7 @@ export class CatalogClientConfiguration {
Catalog.ProtoPackages.ReadPackage,
Catalog.ProtoPackages.WritePackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [
Catalog.ProtoPaths.ReadFilePath,
Catalog.ProtoPaths.WriteFilePath,
+92 -73
View File
@@ -1,16 +1,19 @@
import {
BadRequestException,
Body,
Controller,
Delete,
ForbiddenException,
Get,
Headers,
HttpException,
Inject,
NotFoundException,
Param,
Post,
Put,
Query,
UnsupportedMediaTypeException,
UseFilters,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
@@ -33,9 +36,11 @@ import {
IPreviewResponse,
IUpdateDataRequest,
} from './dtos';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
@ApiTags('Catalog')
@Controller('catalog')
@UseFilters(new GrpcToHttpExceptionFilter())
@Authenticated()
export class CatalogController {
logger: DadosferaLogger;
@@ -52,12 +57,12 @@ export class CatalogController {
PERMISSIONS_GROUPS.CATALOG.permissions.GET,
PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER,
)
async catalogAll(
async searchCatalog(
@User() user: RequestUser,
@Query() query,
): Promise<ICatalogAllResponse> {
const { user_id, customer_name, customer_id, username, permissions } = user;
this.logger.info(`/catalog - Catalog all data assets`, {
this.logger.info(`/catalog - searchCatalog`, {
user_id,
customer_name,
});
@@ -77,13 +82,66 @@ export class CatalogController {
is_data_manager,
});
const res = await this.catalogService.getAllOpenSearchDataAssets(
const res = await this.catalogService.searchDataAssets(
query,
metadata,
customer_id,
);
return { ...res };
return res;
}
@Get('data-asset')
async findByPipelineAndObject(@User() user: RequestUser, @Query() query) {
const { username, user_id, customer_id, customer_name, permissions } = user;
const { pipeline, object } = query;
this.logger.info(`/catalog - ON GET DATA ASSET BY PIPELINE AND OBJECT`, {
username,
customer_name,
});
if (!pipeline || !object) {
throw new BadRequestException('Query params not provided');
}
const is_data_manager = permissions.includes(
PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid,
);
let has_permission = false;
const metadata = PackTheMetadata({
username,
user_id: undefined,
customer_id,
customer_name,
});
const user_roles = await this.catalogService.getUserRolesIds(user_id);
const { data_asset } =
await this.catalogService.getOneDataAssetByPipelineAndObject({
pipeline: query.pipeline,
object: query.object,
customer_id,
metadata,
});
if (data_asset?.owner === username) has_permission = true;
for (const role of user_roles) {
if (data_asset.p_roles.includes(role)) has_permission = true;
}
if (data_asset.p_users.includes(user_id)) has_permission = true;
if (is_data_manager || has_permission) {
delete data_asset.p_roles;
delete data_asset.p_users;
return { data_asset };
}
throw new ForbiddenException(
'You do not have permission to access this data asset.',
);
}
@Get('tags')
@@ -116,13 +174,12 @@ export class CatalogController {
)
async getDataAsset(
@User() user: RequestUser,
@Headers() headers,
@Param('id') id,
@Query('shared') shared?: 'true',
) {
const { username, user_id, customer_id, customer_name, permissions } = user;
const [data_asset_type, data_asset_id] = id.split('-');
this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, {
this.logger.info(`GET /data-asset/${id}`, {
username,
customer_name,
});
@@ -142,29 +199,27 @@ export class CatalogController {
const { data_asset } = await this.catalogService.getOneDataAsset({
customer_id,
data_asset_id,
data_asset_type,
id,
metadata,
});
has_permission =
is_data_manager ||
data_asset?.owner === username ||
(user_roles as Array<any>).some((r) => data_asset.p_roles.includes(r)) ||
data_asset.p_users.includes(user_id);
if (
shared === 'true' &&
(data_asset.share_type === undefined || data_asset.share_type === 'none')
)
throw new NotFoundException();
if (!has_permission)
throw new ForbiddenException(
'You do not have permission to access this data asset.',
);
delete data_asset.p_roles;
delete data_asset.p_users;
if (data_asset?.owner === username) has_permission = true;
for (const role of user_roles) {
if (data_asset.p_roles.includes(role)) has_permission = true;
}
if (data_asset.p_users.includes(user_id)) has_permission = true;
if (is_data_manager || has_permission) {
delete data_asset.p_roles;
delete data_asset.p_users;
return { data_asset };
}
throw new HttpException(
'You do not have permission to access this data asset.',
403,
);
return { data_asset };
}
@Get('data-asset/rls/:id')
@@ -178,7 +233,6 @@ export class CatalogController {
@Param('id') id,
) {
const { username, user_id, customer_id, customer_name, permissions } = user;
const [data_asset_type, data_asset_id] = id.split('-');
this.logger.info(`/catalog - ON GET ONE DASHBOARD METABASE ROUTE`, {
username,
@@ -200,8 +254,7 @@ export class CatalogController {
const { data_asset } = await this.catalogService.getOneDataAsset({
customer_id,
data_asset_id,
data_asset_type,
id,
metadata,
});
@@ -219,9 +272,8 @@ export class CatalogController {
return { data_asset };
}
throw new HttpException(
throw new ForbiddenException(
'You do not have permission to access this data asset.',
403,
);
}
@@ -236,17 +288,8 @@ export class CatalogController {
@Param('id') id,
): Promise<IColumnsMetadataResponse> {
const { customer_name, customer_id, user_id, username } = user;
const data_asset_type = id.split('-')[0];
const supported_data_assets = ['dataset'];
if (!supported_data_assets.includes(data_asset_type)) {
throw new UnsupportedMediaTypeException(
`${data_asset_type} does not have a preview function for the time being.`,
);
}
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
this.logger.info(`/catalog - columns-metadata`, {
user_id,
customer_name,
});
@@ -276,15 +319,6 @@ export class CatalogController {
@Param('id') id,
): Promise<IPreviewResponse> {
const { customer_name, customer_id, user_id, username } = user;
const data_asset_type = id.split('-')[0];
const supported_data_assets = ['dataset'];
if (!supported_data_assets.includes(data_asset_type)) {
throw new UnsupportedMediaTypeException(
`${data_asset_type} does not have a preview function for the time being.`,
);
}
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
user_id,
@@ -315,15 +349,6 @@ export class CatalogController {
@Param('id') id,
): Promise<IDocsResponse> {
const { customer_name, customer_id, user_id, username } = user;
const data_asset_type = id.split('-')[0];
const supported_data_assets = ['dataset'];
if (!supported_data_assets.includes(data_asset_type)) {
throw new UnsupportedMediaTypeException(
`${data_asset_type} does not have a documentation function for the time being.`,
);
}
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
user_id,
@@ -351,7 +376,7 @@ export class CatalogController {
async updateDataAsset(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language,
@Param('id') id,
@Param('id') data_asset_id,
@Body() body: IUpdateDataRequest,
): Promise<IOneDataAsset> {
const { customer_id, customer_name, user_id, username } = user;
@@ -365,7 +390,7 @@ export class CatalogController {
const result = await this.catalogService.updateOneDataAsset({
body,
data_asset_id: id,
data_asset_id,
customer_id,
metadata,
});
@@ -384,11 +409,10 @@ export class CatalogController {
async manageDataAssetDocs(
@User() user: RequestUser,
@Headers() headers,
@Param('id') data_asset_id,
@Param('id') table_id: string,
@Body('docs') docs: string,
) {
const { user_id, customer_name } = user;
const [, id] = data_asset_id.split('-');
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
user_id,
@@ -396,7 +420,7 @@ export class CatalogController {
});
const res = await this.catalogService.createDataDocs({
table_id: id,
table_id,
docs,
info: {
customer: customer_name,
@@ -503,10 +527,7 @@ export class CatalogController {
PERMISSIONS_GROUPS.CATALOG.permissions.DELETE,
PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER,
)
async deleteDataAsset(
@Param('id') data_asset_id: string,
@User() user: RequestUser,
) {
async deleteDataAsset(@Param('id') id: string, @User() user: RequestUser) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
@@ -514,10 +535,8 @@ export class CatalogController {
user_id,
username,
});
const [type, id] = data_asset_id.split('-');
const response = await this.catalogService.deleteDataAsset(
{ id, type },
{ id, type: undefined },
metadata,
);
+105 -170
View File
@@ -18,6 +18,7 @@ import { CatalogClientConfiguration } from './catalog-client';
import { UsersService } from '../users/users.service';
import { RolesService } from '../roles/roles.service';
import { Metadata } from '@grpc/grpc-js';
import { IUpdateDataRequest } from './dtos';
class CatalogService implements OnModuleInit {
catalogReadService: ReadService.CatalogReadServices;
@@ -60,15 +61,11 @@ class CatalogService implements OnModuleInit {
async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) {
this.logger.info('CatalogService - Manage Data assets permissions');
if (!data.embed) data.embed = undefined;
return lastValueFrom(
this.catalogWriteService.CreateDataAsset(data, metadata),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 404,
);
});
);
}
async managePermissions(data: Messages.ManagePermissionRequest, metadata) {
@@ -144,34 +141,12 @@ class CatalogService implements OnModuleInit {
return roles_ids;
}
async getUsernamesAndRolesByCustomerId(customerId: string) {
const resultUsers = await this.userService.findAllUsersByCustomerId(
customerId,
);
const resultRoles = await this.roleService.roleSearch(
{},
{ customer_id: customerId },
);
const customer_users = resultUsers.users.map((user) => {
return {
id: user.id,
username: user.username,
};
});
const customer_roles = resultRoles.roles.map((role) => {
return {
id: role.id,
name: role.name,
};
});
return [customer_users, customer_roles];
}
async getAllOpenSearchDataAssets(query, metadata, customer_id) {
this.logger.info('CatalogService - getAllOpenSearchDashboard');
async searchDataAssets(
query: Record<string, any>,
metadata: Metadata,
customer_id: string,
) {
this.logger.info('CatalogService - searchDataAssets');
const { search, page, size, sort_by, order, ...filters } = query;
@@ -187,171 +162,103 @@ class CatalogService implements OnModuleInit {
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 404,
);
});
);
const result = JSON.parse(data_assets);
const [customer_users, customer_roles] =
await this.getUsernamesAndRolesByCustomerId(customer_id);
const response = await this.getAssetsUsersAndRoles(
result.data_assets,
customer_id,
);
for (const asset of result.data_assets) {
const condition =
asset.owner && !asset.owner.includes('@') ? true : false;
const result = condition
? await this.userService.findOneById(asset.owner).catch(() => null)
: null;
const owner = result !== null ? result.user.username : null;
const roles = [];
const users = [];
for (const role of customer_roles) {
for (const role_id of asset.roles) {
if (role.id === role_id) roles.push(role);
}
}
for (const user of customer_users) {
for (const user_id of asset.users) {
if (user.id === user_id) users.push(user);
}
}
Object.assign(asset, {
...asset,
owner,
roles,
users,
});
}
return { data_assets: result.data_assets, total };
return { data_assets: response, total };
}
async getOneDataAsset({
data_asset_id,
data_asset_type,
customer_id,
metadata,
async getOneDataAsset(data: {
id: string;
customer_id: string;
metadata: Metadata;
}) {
const { customer_id, id, metadata } = data;
const { data_asset } = await lastValueFrom(
this.catalogReadService.GetOneDataAsset(
{ id: data_asset_id, type: data_asset_type },
{ id, type: undefined },
metadata,
),
);
const opensearchTable = JSON.parse(data_asset);
let asset = JSON.parse(data_asset);
asset = {
...asset,
p_roles: asset.roles,
p_users: asset.users,
};
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
const result = opensearchTable.owner
? await this.userService
.findOneById(opensearchTable.owner)
.catch(() => null)
: null;
const owner = result !== null ? result.user.username : null;
const [customer_users, customer_roles] =
await this.getUsernamesAndRolesByCustomerId(customer_id);
const users = [];
const roles = [];
for (const role of customer_roles) {
for (const role_id of opensearchTable.roles) {
if (role.id === role_id) roles.push(role);
}
}
for (const user of customer_users) {
for (const user_id of opensearchTable.users) {
if (user.id === user_id) users.push(user);
}
}
Object.assign(opensearchTable, {
p_roles: opensearchTable.roles,
p_users: opensearchTable.users,
roles,
users,
owner,
});
return { data_asset: opensearchTable };
return { data_asset: asset[0] };
}
async updateOneDataAsset({ data_asset_id, customer_id, body, metadata }) {
async getOneDataAssetByPipelineAndObject(data: {
customer_id: string;
pipeline: string;
object: string;
metadata: Metadata;
}) {
const { customer_id, metadata, pipeline, object } = data;
const { data_asset } = await lastValueFrom(
this.catalogReadService.GetOneDataAssetByPipelineAndObject(
{ pipeline, object },
metadata,
),
);
let asset = JSON.parse(data_asset);
asset = {
...asset,
p_roles: asset.roles,
p_users: asset.users,
};
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
return { data_asset: asset[0] };
}
async updateOneDataAsset(data: {
data_asset_id: string;
customer_id: string;
body: IUpdateDataRequest;
metadata: Metadata;
}) {
const { body, customer_id, data_asset_id, metadata } = data;
const { data_asset } = await lastValueFrom(
this.catalogWriteService.UpdateDataAsset(
{ id: data_asset_id, changes: JSON.stringify(body) },
metadata,
),
);
let asset = JSON.parse(data_asset);
asset = {
...asset,
p_roles: asset.roles,
p_users: asset.users,
};
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
const opensearchTable = JSON.parse(data_asset);
const result = opensearchTable.owner
? await this.userService
.findOneById(opensearchTable.owner)
.catch(() => null)
: null;
const owner = result !== null ? result.user.username : null;
const [customer_users, customer_roles] =
await this.getUsernamesAndRolesByCustomerId(customer_id);
const users = [];
const roles = [];
for (const role of customer_roles) {
for (const role_id of opensearchTable.roles) {
if (role.id === role_id) roles.push(role);
}
}
for (const user of customer_users) {
for (const user_id of opensearchTable.users) {
if (user.id === user_id) users.push(user);
}
}
Object.assign(opensearchTable, {
p_roles: opensearchTable.roles,
p_users: opensearchTable.users,
roles,
users,
owner,
});
return { data_asset: opensearchTable };
return { data_asset: asset[0] };
}
async getDataDocs(data_asset_id: string, metadata: Metadata) {
const [data_asset_type, id] = data_asset_id.split('-');
async getDataDocs(id: string, metadata: Metadata) {
const { documentation } = await lastValueFrom(
this.catalogReadService.GetDatasetDoc(
{ id, type: data_asset_type },
metadata,
),
this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata),
);
console.log(documentation);
const docs = JSON.parse(documentation);
return docs;
}
async getDatasetPreview(data_asset_id: string, metadata: Metadata) {
const [data_asset_type, id] = data_asset_id.split('-');
async getDatasetPreview(id: string, metadata: Metadata) {
const { preview } = await lastValueFrom(
this.catalogReadService.GetDatasetPreview(
{ id, type: data_asset_type },
{ id, type: undefined },
metadata,
),
);
@@ -359,12 +266,10 @@ class CatalogService implements OnModuleInit {
return result;
}
async getDatasetColumnsMetadata(data_asset_id: string, metadata: Metadata) {
const [data_asset_type, id] = data_asset_id.split('-');
async getDatasetColumnsMetadata(id: string, metadata: Metadata) {
const { columns_metadata } = await lastValueFrom(
this.catalogReadService.GetDatasetColumnsMetadata(
{ id, type: data_asset_type },
{ id, type: undefined },
metadata,
),
);
@@ -398,6 +303,36 @@ class CatalogService implements OnModuleInit {
return response;
}
async getAssetsUsersAndRoles(data_assets: Array<any>, customer_id: string) {
const { users: customer_users } =
await this.userService.findAllUsersByCustomerId(customer_id);
const { roles: customer_roles } = await this.roleService.roleSearch(
{},
{ customer_id },
);
return data_assets.map((data_asset) => {
const owner = customer_users.find(
(u) => u.id === data_asset.owner,
)?.username;
const roles = [];
const users = [];
for (const role_id of data_asset.roles) {
const role = customer_roles.find((r) => r.id === role_id);
if (role) roles.push({ id: role.id, name: role.name });
}
for (const user_id of data_asset.users) {
const user = customer_users.find((r) => r.id === user_id);
if (user) users.push({ id: user.id, username: user.username });
}
return {
...data_asset,
roles,
users,
owner,
} as typeof data_asset;
});
}
}
export { CatalogService };
+21 -5
View File
@@ -1,6 +1,15 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { CreateDataAssetRequest } from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
export enum DataAssetShareType {
none = 'none',
public = 'public',
private = 'private',
}
export class EmbedObject {
@ApiProperty()
url: string;
}
export class IRole {
@ApiProperty()
name: string;
@@ -49,8 +58,8 @@ export class IDataAsset {
data_encrypted?: string;
@ApiPropertyOptional()
num_rows?: number;
@ApiPropertyOptional()
display_name?: string;
@ApiProperty()
display_name: string;
@ApiProperty()
pipeline_id: string;
@ApiProperty()
@@ -75,12 +84,14 @@ export class IDataAsset {
parameters?: string[];
@ApiPropertyOptional()
embedding_params?: object;
@ApiPropertyOptional()
created_at?: string;
@ApiProperty()
created_at: string;
@ApiPropertyOptional()
updated_at?: string;
@ApiProperty({ type: [IComment] })
comments: IComment[];
@ApiPropertyOptional()
embed?: EmbedObject;
}
export class IOneDataAsset {
@@ -127,8 +138,11 @@ export class IUpdateDataRequest {
description: string;
@ApiProperty()
tags: string[];
@ApiPropertyOptional()
embed: EmbedObject;
@ApiPropertyOptional({ enum: DataAssetShareType })
share_type: DataAssetShareType;
}
export class ICreateDataAsset implements CreateDataAssetRequest {
@ApiProperty()
display_name: string;
@@ -140,6 +154,8 @@ export class ICreateDataAsset implements CreateDataAssetRequest {
external_url: string;
@ApiProperty()
location: string;
@ApiPropertyOptional()
embed: EmbedObject;
}
export class IPreview {
@@ -6,14 +6,15 @@ import {
} from '@nestjs/microservices';
import { ConnectionTest } from '@dadosfera/protospack-v2';
const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
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(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [ConnectionTest.ProtoPaths.ReadFilePath],
loader: {
keepCase: true,
+3 -4
View File
@@ -6,6 +6,8 @@ import {
} from '@nestjs/microservices';
import { ConnectionManager } from '@dadosfera/protospack-v2';
const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
export class ConnectionClientConfiguration {
public name = 'ConnectionClientConfiguration';
private config: GrpcOptions = {
@@ -16,10 +18,7 @@ export class ConnectionClientConfiguration {
ConnectionManager.ProtoPackages.WritePackage,
ConnectionManager.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [
ConnectionManager.ProtoPaths.WriteFilePath,
ConnectionManager.ProtoPaths.ReadFilePath,
+6 -36
View File
@@ -1,9 +1,4 @@
import {
OnModuleInit,
Inject,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { OnModuleInit, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ConnectionManager } from '@dadosfera/protospack-v2';
import { lastValueFrom } from 'rxjs';
@@ -86,12 +81,7 @@ export class ConnectionClientService implements OnModuleInit {
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async updateConnection(
@@ -112,12 +102,7 @@ export class ConnectionClientService implements OnModuleInit {
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async deleteConnection({ body, metadata }): Promise<ConnectionRes> {
@@ -130,12 +115,7 @@ export class ConnectionClientService implements OnModuleInit {
},
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async getAllConnections({ body, metadata }): Promise<ConnectionsRes> {
@@ -146,12 +126,7 @@ export class ConnectionClientService implements OnModuleInit {
{ search, filters: JSON.stringify(filters), size, page },
metadata,
),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async getConnectionDetails({
@@ -162,12 +137,7 @@ export class ConnectionClientService implements OnModuleInit {
return lastValueFrom(
this.connectionServiceRead.GetConnectionDetails({ id }, metadata),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async INTERNAL_getAllConnections({
@@ -9,6 +9,7 @@ import {
Get,
Headers,
Query,
UseFilters,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ConnectionClientService } from './client.service';
@@ -28,8 +29,10 @@ import {
} from './dtos/connection';
import { CreateConnectionDto } from './dtos/connection';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
const connectionPermissions = PERMISSIONS_GROUPS.CONNECTION.permissions;
@UseFilters(new GrpcToHttpExceptionFilter())
@ApiTags('connections')
@ApiBearerAuth()
@Authenticated()
@@ -161,11 +164,15 @@ export class ConnectionController {
@Headers('Dadosfera-Lang') language,
@User() user: RequestUser,
@Param('id') id,
@Query('details') details,
@Query() queries,
): Promise<ConnectionDetailsRes> {
this.logger.info('/connections - Get Connection Details');
if (!language) language = 'en-us';
console.log(queries);
const { details, sensitive } = queries;
const { user_id, customer_id, customer_name, username } = user;
const metadata = PackTheMetadata({
user_id,
@@ -174,6 +181,7 @@ export class ConnectionController {
username,
details,
language,
sensitive,
});
const response: any = await this.clientService.getConnectionDetails({
+3 -4
View File
@@ -6,6 +6,8 @@ import {
} from '@nestjs/microservices';
import { ConnectorManager } from '@dadosfera/protospack-v2';
const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
export class ConnectorClientConfiguration {
public name = 'ConnectorClientConfiguration';
private config: GrpcOptions = {
@@ -16,10 +18,7 @@ export class ConnectorClientConfiguration {
ConnectorManager.ProtoPackages.WritePackage,
ConnectorManager.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [
ConnectorManager.ProtoPaths.WriteFilePath,
ConnectorManager.ProtoPaths.ReadFilePath,
+22 -160
View File
@@ -83,163 +83,30 @@ export class ConnectorClientService implements OnModuleInit {
);
});
const responseParsed = JSON.parse(res.message);
const connectorErrors = responseParsed.items
.filter((i) => i.update._index === 'connectors' && i.update.error)
const connectorsResponse = responseParsed.items.filter(
(i) => i.update._index === process.env.CONNECTORS_INDEX,
);
const connectorErrors = connectorsResponse
.filter((i) => i.update.error)
.map((i) => ({
id: i.update._id,
reason: i.update.error,
status: i.error.status,
}));
const connectionControlsErrors = responseParsed.items
.filter(
(i) => i.update._index === 'connection_controls' && i.update.error,
)
.map((i) => ({
id: i.update._id,
reason: i.update.error,
status: i.error.status,
}));
const connectionStepsErrors = responseParsed.items
.filter((i) => i.update._index === 'connection_steps' && i.update.error)
.map((i) => ({
id: i.update._id,
reason: i.update.error,
status: i.error.status,
}));
const configControlsErrors = responseParsed.items
.filter((i) => i.update._index === 'config_controls' && i.update.error)
.map((i) => ({
id: i.update._id,
reason: i.update.error,
status: i.error.status,
}));
const connectorCreated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connectors' && i.update.result === 'created',
)
.map((i) => ({
id: i.update._id,
}));
const connectionControlsCreated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_controls' &&
i.update.result === 'created',
)
.map((i) => ({
id: i.update._id,
}));
const connectionStepsCreated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_steps' &&
i.update.result === 'created',
)
.map((i) => ({
id: i.update._id,
}));
const configControlsCreated = responseParsed.items
.filter(
(i) =>
i.update._index === 'config_controls' &&
i.update.result === 'created',
)
.map((i) => ({
id: i.update._id,
}));
const connectorUpdated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connectors' && i.update.result === 'updated',
)
.map((i) => ({
id: i.update._id,
}));
const connectionControlsUpdated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_controls' &&
i.update.result === 'updated',
)
.map((i) => ({
id: i.update._id,
}));
const connectionStepsUpdated = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_steps' &&
i.update.result === 'updated',
)
.map((i) => ({
id: i.update._id,
}));
const configControlsUpdated = responseParsed.items
.filter(
(i) =>
i.update._index === 'config_controls' &&
i.update.result === 'updated',
)
.map((i) => ({
id: i.update._id,
}));
const connectorNoop = responseParsed.items
.filter(
(i) => i.update._index === 'connectors' && i.update.result === 'noop',
)
.map((i) => ({
id: i.update._id,
}));
const connectionControlsNoop = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_controls' &&
i.update.result === 'noop',
)
.map((i) => ({
id: i.update._id,
}));
const connectionStepsNoop = responseParsed.items
.filter(
(i) =>
i.update._index === 'connection_steps' && i.update.result === 'noop',
)
.map((i) => ({
id: i.update._id,
}));
const configControlsNoop = responseParsed.items
.filter(
(i) =>
i.update._index === 'config_controls' && i.update.result === 'noop',
)
.map((i) => ({
id: i.update._id,
id: i.update?._id,
reason: i.update?.error,
status: i.error?.status,
}));
const connectorCreated = connectorsResponse
.filter((i) => i.update.result === 'created')
.map((i) => i.update._id);
const connectorUpdated = connectorsResponse
.filter((i) => i.update.result === 'updated')
.map((i) => i.update._id);
const connectorNoop = connectorsResponse
.filter((i) => i.update.result === 'noop')
.map((i) => i.update._id);
return {
errors: {
connectors: connectorErrors,
connection_controls: connectionControlsErrors,
connection_steps: connectionStepsErrors,
config_controls: configControlsErrors,
},
created: {
connectors: connectorCreated,
connection_controls: connectionControlsCreated,
connection_steps: connectionStepsCreated,
config_controls: configControlsCreated,
},
updated: {
connectors: connectorUpdated,
connection_controls: connectionControlsUpdated,
connection_steps: connectionStepsUpdated,
config_controls: configControlsUpdated,
},
noop: {
connectors: connectorNoop,
connection_controls: connectionControlsNoop,
connection_steps: connectionStepsNoop,
config_controls: configControlsNoop,
},
errors: connectorErrors,
created: connectorCreated,
updated: connectorUpdated,
noop: connectorNoop,
};
}
@@ -318,12 +185,7 @@ export class ConnectorClientService implements OnModuleInit {
version,
plugin,
}),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
);
}
async updateConnector({ plugin, changes }) {
@@ -19,7 +19,6 @@ 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 { DeleteConnectorDto } from './dtos/delete-connector';
import { GetAllDto } from './dtos/get-all';
import { RemoveTagDto } from './dtos/remove-tag';
import { UpdateDto } from './dtos/update';
@@ -274,7 +273,7 @@ export class ConnectorController {
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.DELETE)
async deleteConnector(
@Param('plugin') plugin: string,
@Body() { version }: DeleteConnectorDto,
@Query('version') version: string,
) {
this.logger.info('/upload - Upload Connector Route');
+3 -4
View File
@@ -9,6 +9,8 @@ import {
ProtoPaths,
} from '@dadosfera/protospack-v2/dist/lib/Duc';
const isLocalConnection = !!process.env.DUC_URL?.includes('0.0.0.0');
export class DucClient {
public name = 'DucClient';
@@ -17,10 +19,7 @@ export class DucClient {
options: {
url: process.env.DUC_URL,
package: [ProtoPackages.WritePackage, ProtoPackages.ReadPackage],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [ProtoPaths.WriteFilePath, ProtoPaths.ReadFilePath],
loader: {
keepCase: true,
+3 -5
View File
@@ -2,10 +2,11 @@ import { Input } from '@dadosfera/protospack-v2';
import { credentials } from '@grpc/grpc-js';
import {
ClientProviderOptions,
GrpcOptions,
Transport,
type GrpcOptions,
} from '@nestjs/microservices';
const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
export class InputsGrpcClient {
public readonly name = 'InputsGrpcClient';
private config: GrpcOptions = {
@@ -16,10 +17,7 @@ export class InputsGrpcClient {
Input.ProtoPackages.WritePackage,
Input.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [
Input.ProtoPaths.WriteFilePath,
Input.ProtoPaths.ReadFilePath,
+60 -2
View File
@@ -15,9 +15,55 @@ export class MixpanelController {
delete body.info;
const mixpanel = init(this.mixpanelToken);
const separator = user.username.includes('-') ? '-' : '.';
const removeValues = [
'.dadosferatech.dadosfera',
'.demo.dadosfera',
'.dadosferademo',
'.dadosferarh.dadosfera',
'.dadosferatech.dadosfera2',
'.dadosferatech.dadosfera',
'.dadosfera.fin',
'.dadosferafin.dadosfera',
'.praxio.dadosfera',
'.dadosfera.tech',
'.treinamentos@dadosfera.ai',
'.dadosfera2',
'.treinamentosfera',
'.dadosfera',
];
let username = user.username;
removeValues.forEach((value) => {
username = username.replace(value, '');
});
username = username.split('@')?.[0];
username = username.split('+')?.[0];
let firstName = username
.substring(0, username.indexOf(separator))
.replace('dadosfera', '');
let lastName = username
.substring(username.lastIndexOf(separator) + 1)
.replace('dadosfera', '');
if (!firstName) {
firstName = lastName;
lastName = '';
}
firstName = this.capitalize(firstName);
lastName = this.capitalize(lastName);
await mixpanel.people.set(user.username, {
$name: user.username,
$email: user.username,
$first_name: firstName,
$last_name: lastName,
$name: this.getFullName(firstName, lastName),
$email: user.username.includes('@')
? user.username
: user.username + '@dadosfera.ai',
customer_name: user.customer_name,
});
@@ -30,4 +76,16 @@ export class MixpanelController {
return { id, body, user: user.username };
}
capitalize(sentence: string): string {
if (!sentence) {
return '';
}
return sentence[0].toUpperCase() + sentence.substring(1);
}
getFullName(firstName: string, lastName: string) {
return `${firstName}${lastName ? ' ' + lastName : ''}`;
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { Module } from '@nestjs/common';
import { getSecreteFromSecreteManager } from 'src/utils/SecretManager';
import { getSecretFromSecretsManager } from 'src/utils/SecretManager';
import { MixpanelController } from './mixpanel.controller';
@Module({
@@ -7,7 +7,7 @@ import { MixpanelController } from './mixpanel.controller';
providers: [
{
provide: 'MIXPANEL_TOKEN',
useValue: getSecreteFromSecreteManager(
useValue: getSecretFromSecretsManager(
`${process.env.ENV}/root/mixpanel_token`,
),
},
+10 -4
View File
@@ -148,10 +148,16 @@ export class OauthController {
const { customer_id, customer_name, user_id } = connectionInfo;
try {
const response = await this.connectionService.createConnection(
connectionInfo,
PackTheMetadata({ customer_id, customer_name, user_id }),
);
const response = connectionInfo.id
? await this.connectionService.updateConnection(
connectionInfo.id,
connectionInfo,
PackTheMetadata({ customer_id, customer_name, user_id }),
)
: 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) {
+2
View File
@@ -23,6 +23,7 @@ export class OauthService {
user_id,
customer_id,
customer_name,
id,
} = query;
const properties = { plugin, credentials_type: 'oauth' };
@@ -45,6 +46,7 @@ export class OauthService {
type,
properties,
plugin,
id,
};
return jwt.sign({ ...newConnection }, process.env.JWT_PRIVATE_KEY);
}
@@ -44,7 +44,8 @@ export class FacebookStrategy extends PassportStrategy(Strategy) {
req,
'application',
);
options.scope = 'ads_read ads_management';
options.scope =
'pages_show_list ads_read pages_read_engagement ads_management';
super.authenticate(req, options);
}
}
+3 -4
View File
@@ -6,6 +6,8 @@ import {
import { PipelinePackages, PipelineProtoFilePath } from 'protospack';
import { credentials } from '@grpc/grpc-js';
const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
export class PipelinesClientConfiguration {
public name = 'PipelinesClientConfiguration';
private config: GrpcOptions = {
@@ -13,10 +15,7 @@ export class PipelinesClientConfiguration {
options: {
url: process.env.PIFACTORY_URL,
package: PipelinePackages,
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: PipelineProtoFilePath,
loader: {
keepCase: true,
+39
View File
@@ -66,3 +66,42 @@ export interface IGetPipelineLogsRequest {
id: string;
details: string;
}
export class IInitUploadCSVFile {
@ApiProperty()
file_name: string;
@ApiProperty()
parts: number;
}
export class ICompleteUploadCSVFile {
@ApiProperty()
upload_id: string;
@ApiProperty()
file_name: string;
@ApiProperty()
parts: { ETag: string; PartNumber: number }[];
}
export class ICreatePipelineCSVFile {
@ApiProperty()
name: string;
@ApiProperty()
file_name: string;
@ApiProperty()
description: string;
@ApiProperty()
encoding: string;
@ApiProperty()
sep: string;
@ApiProperty()
header: boolean;
}
+3 -4
View File
@@ -9,6 +9,8 @@ import {
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
import { credentials } from '@grpc/grpc-js';
const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
export class PipelinesClientConfiguration {
public name = 'PipelinesClientConfiguration';
private config: GrpcOptions = {
@@ -16,10 +18,7 @@ export class PipelinesClientConfiguration {
options: {
url: process.env.PIFACTORY_URL,
package: [ProtoPackages.ReadPackage, ProtoPackages.WritePackage],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [ProtoPaths.ReadFilePath, ProtoPaths.WriteFilePath],
loader: {
keepCase: true,
+188 -7
View File
@@ -12,13 +12,18 @@ import {
UseFilters,
HttpCode,
HttpStatus,
Patch,
HttpException,
} from '@nestjs/common';
import {
ApiCreatedResponse,
ApiNoContentResponse,
ApiTags,
} from '@nestjs/swagger';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import {
AuthenticateCondition,
RequireAllPermissions,
} from 'src/authentication/authentication.decorator';
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
import { PipelinesService } from './pipelines.service';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
@@ -27,7 +32,13 @@ import { RequestUser, User } from 'src/authentication/user.decorator';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { PipelinesService as OldPipelineService } from 'src/modules/pipelines/pipelines.service';
import { ICreatePipelineV2Req, IPipelineV2 } from './interfaces';
import {
ICompleteUploadCSVFile,
ICreatePipelineCSVFile,
ICreatePipelineV2Req,
IPipelineV2,
IInitUploadCSVFile,
} from './interfaces';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
@UseFilters(new GrpcToHttpExceptionFilter())
@@ -45,6 +56,10 @@ import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filt
action = 'UPDATE';
break;
case 'PATCH':
action = 'UPDATE';
break;
default:
action = req.method;
}
@@ -116,6 +131,39 @@ export class PipelinesController {
};
}
@Get('/download-logs')
async downloadLogs(
@User() user: RequestUser,
@Headers('Dadosfera-Lang') language,
@Query() query,
) {
this.logger.info('PipelinesController - downloadLogs', { user });
if (!language) language = 'en-us';
if (!query) {
throw new HttpException('Query params not provided', 400);
}
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
language,
});
const url = await this.pipelinesClientService.downloadLogs(
query.pipeline_run_id,
metadata,
);
return {
url,
};
}
@Get(':id/config')
async getPipelineproperties(
@Headers('Dadosfera-Lang') language,
@@ -130,6 +178,20 @@ export class PipelinesController {
return this.pipelinesClientService.findOneProperties(id, metadata);
}
@Get(':id/objects')
async getPipelineObjects(
@Headers('Dadosfera-Lang') language,
@User() user: RequestUser,
@Param('id') id,
) {
this.logger.info('PipelinesController - getPipelineObjects', { user });
const metadata = PackTheMetadata({
...user,
language: language || 'pt-br',
});
return this.pipelinesClientService.findOneObjects(id, metadata);
}
@Get('/:id')
async findOne(
@Headers('Dadosfera-Lang') language,
@@ -175,20 +237,49 @@ export class PipelinesController {
@Put('/:id')
async update(
@Headers('Dadosfera-Lang') language,
@Body() updatePipelineDto,
@Param('id') id,
@User() user: RequestUser,
) {
this.logger.info('PipelinesController - findOne', { user });
this.logger.info('PipelinesController - update', { user });
const { info } = updatePipelineDto;
delete updatePipelineDto.info;
const response = await this.pipelinesClientService.update({
...updatePipelineDto,
info,
id,
const { customer_id, customer_name, user_id, username } = user;
if (!language) language = 'en-us';
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
language,
});
const response = await this.pipelinesClientService.update(
{
...updatePipelineDto,
info,
id,
},
metadata,
);
this.logger.info('PipelinesController - update: OK', { user });
return response;
}
@Patch('/:id')
async updateByPatch(
@Headers('Dadosfera-Lang') language,
@Body() updatePipelineDto,
@Param('id') id,
@User() user: RequestUser,
) {
this.logger.info('PipelinesController - patch', { user });
const response = await this.update(language, updatePipelineDto, id, user);
this.logger.info('PipelinesController - patch: OK', { user });
return response;
}
@@ -206,4 +297,94 @@ export class PipelinesController {
await this.pipelinesClientService.remove({ id, metadata, user });
this.logger.info('PipelinesController - delete: OK');
}
@Post('/init-upload')
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
async initUploadFile(
@User() user: RequestUser,
@Body() body: IInitUploadCSVFile,
) {
this.logger.info('/upload - Init Upload File Route');
const metadata = PackTheMetadata({ ...user });
const { file_name, parts } = body;
const { urls, upload_id } =
await this.pipelinesClientService.initUploadFile(
{
name: file_name,
parts: String(parts),
},
metadata,
);
if (!urls) {
this.logger.info('pipeline/upload - Failed File pipeline');
throw new HttpException(
'Upload failed, try again in a few minutes, if the problem persists, contact support.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
return { urls: JSON.parse(urls), upload_id };
}
@Post('/complete-upload')
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
async completeUploadFile(
@User() user: RequestUser,
@Body() body: ICompleteUploadCSVFile,
) {
this.logger.info('/upload - Complete Upload File Route');
const metadata = PackTheMetadata({ ...user });
const { upload_id, parts, file_name } = body;
return await this.pipelinesClientService.completeUploadFile(
{ upload_id, parts, file_name },
metadata,
);
}
@Post('/file')
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
async uploadedFile(
@User() user: RequestUser,
@Body() body: ICreatePipelineCSVFile,
) {
this.logger.info('/upload - Upload Connector Route');
const metadata = PackTheMetadata({ ...user });
const { name, sep, header, encoding, description, file_name } = body;
const source_prefix = `${user.customer_name}/${file_name}`;
const upload_pipeline = {
connection_id: process.env.UPLOAD_FILE_AGENT_CONNECTION,
connector_name: 'Amazon S3',
connector_plugin: 'aws_s3',
connector_version: '1.0.0',
image_url: 'https://assets.dadosfera.ai/images/connectors/csv.svg',
name,
description,
transformations_ids: [],
tags: [],
cron: '@once',
config: { cron: '@once', tables: [] },
properties: {
engine: 'csv',
source_bucket: process.env.BUCKET_CUSTOMER_CSV_ASSETS,
source_prefix,
file_format_params: { sep, encoding, header: Boolean(header) },
is_a_upload_csv: true,
},
input_id: undefined,
};
const pipeline = await this.pipelinesClientService.create(
upload_pipeline,
metadata,
);
return pipeline;
}
}
+106 -31
View File
@@ -1,7 +1,8 @@
import {
BadRequestException,
HttpException,
HttpStatus,
Inject,
InternalServerErrorException,
OnModuleInit,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
@@ -23,6 +24,8 @@ import { InputsService } from '../inputs/inputs.service';
import { RequestUser } from 'src/authentication/user.decorator';
import { TransformationsService } from '../transformations/transformations.service';
import { getObjValueFromPath } from 'src/utils/ObjValueFromPath';
import ErrorCodes from 'src/utils/errorCodes';
import ErrorBuilder from 'src/utils/ErrorBuilder';
export class PipelinesService implements OnModuleInit {
logger: DadosferaLogger;
@@ -99,7 +102,7 @@ export class PipelinesService implements OnModuleInit {
const [errorType, message] = error.details.split('|');
throw new BadRequestException(message);
}
throw new InternalServerErrorException(error.details);
throw new ErrorBuilder(error.details);
});
this.logger.info('Done');
@@ -140,11 +143,17 @@ export class PipelinesService implements OnModuleInit {
return findOnePipelineResponse;
}
async update(UpdatePipelineRequest: Messages.PipelineV2UpdateRequest) {
async update(
UpdatePipelineRequest: Messages.PipelineV2UpdateRequest,
metadata,
) {
this.logger.info('PipelinesClientService - Update');
const updatePipelineResponse = await lastValueFrom(
this.pipelineWriteService.PipelineV2Update(UpdatePipelineRequest),
this.pipelineWriteService.PipelineV2Update(
UpdatePipelineRequest,
metadata,
),
);
this.logger.info('Done');
@@ -158,45 +167,53 @@ export class PipelinesService implements OnModuleInit {
customer_id: user.customer_id,
customer: user.customer_name,
};
let existsInProduct = true;
const { pipeline } = await lastValueFrom(
this.pipelineReadService.PipelineV2FindOne({ id }, metadata),
);
).catch((e) => {
if (e.details === ErrorCodes.PIPELINE.NOT_FOUND) existsInProduct = false;
else throw new ErrorBuilder(e.details);
return { pipeline: undefined };
});
await lastValueFrom(
this.pipelineWriteService.PipelineV2Remove({ id }, metadata),
);
//{pipeline:{tables: {tables: [], input_id: ''}}}
const input = pipeline.config.tables
? JSON.parse(pipeline.config.tables)
: null;
if (input)
await this.inputsService
.remove({
id: input.input_id,
info,
})
.catch((error) =>
this.logger.error('Could not delete input', {
data: { input, error },
}),
);
const transformations: { id: string }[] = pipeline.transformations
? JSON.parse(pipeline.transformations)
: null;
if (transformations && transformations.length)
for (const transformation of transformations) {
await this.transformationsService
if (existsInProduct) {
//{pipeline:{tables: {tables: [], input_id: ''}}}
const input = pipeline.config.tables
? JSON.parse(pipeline.config.tables)
: null;
if (input)
await this.inputsService
.remove({
id: transformation.id,
id: input.input_id,
info,
})
.catch((error) =>
this.logger.error('Could not delete transformation', {
data: { transformation, error },
this.logger.error('Could not delete input', {
data: { input, error },
}),
);
}
const transformations: { id: string }[] = pipeline.transformations
? JSON.parse(pipeline.transformations)
: null;
if (transformations && transformations.length)
for (const transformation of transformations) {
await this.transformationsService
.remove({
id: transformation.id,
info,
})
.catch((error) =>
this.logger.error('Could not delete transformation', {
data: { transformation, error },
}),
);
}
}
}
async findOneProperties(id: string, metadata: Metadata) {
@@ -232,4 +249,62 @@ export class PipelinesService implements OnModuleInit {
},
};
}
async findOneObjects(id: string, metadata: Metadata) {
this.logger.info('PipelinesClientService - FindOneObjects');
const { objects } = await lastValueFrom(
this.pipelineReadService.PipelineV2FindObjects({ id }, metadata),
);
this.logger.info('Done');
return { objects: JSON.parse(objects) };
}
async initUploadFile(uploadFile, metadata) {
const body: Messages.PipelineV2UploadFileRequest = {
name: uploadFile.name,
parts: uploadFile.parts,
};
return lastValueFrom(
this.pipelineWriteService.PipelineV2UploadFile(body, metadata),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
async downloadLogs(pipeline_run_id: string, metadata: Metadata) {
this.logger.info('PipelinesClientService - DownloadLogs');
const { url } = await lastValueFrom(
this.pipelineReadService.PipelineV2DownloadLogs(
{ pipeline_run_id },
metadata,
),
);
this.logger.info('Done');
return url;
}
async completeUploadFile(uploadFile, metadata) {
const body: Messages.PipelineV2CompleteUploadFileRequest = {
upload_id: uploadFile.upload_id,
parts: JSON.stringify(uploadFile.parts),
name: uploadFile.file_name,
};
return lastValueFrom(
this.pipelineWriteService.PipelineV2CompleteUploadFile(body, metadata),
).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
}
}
@@ -2,7 +2,7 @@ import { Body, Controller, HttpException, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import axios from 'axios';
import { User, RequestUser } from 'src/authentication/user.decorator';
import { getSecreteFromSecreteManager } from 'src/utils/SecretManager';
import { getSecretFromSecretsManager } from 'src/utils/SecretManager';
import { INote } from './dtos';
@ApiTags('Productboard')
@@ -18,7 +18,7 @@ export class ProductboardController {
: username + `@${customer_name}.default`;
const path = process.env.PB_TOKEN_PATH;
const token = await getSecreteFromSecreteManager(path);
const token = await getSecretFromSecretsManager(path);
const response = await axios
.post(
+8
View File
@@ -25,6 +25,8 @@ export class RoleDto {
customer?: Customer;
@ApiProperty()
usage: string;
@ApiPropertyOptional({ description: 'Whether this role can be untoggled' })
canUntoggle?: boolean;
}
export class CustomerRole extends OmitType(RoleDto, ['customer']) {}
@@ -34,11 +36,17 @@ export enum TrueOrFalseEnum {
true = 'true',
false = 'false',
}
export enum CanUntoggleLogicEnum {
data_assets_sharing = 'data_assets_sharing',
}
export class GetRolesByCustomerReq {
@ApiPropertyOptional()
permissionId?: string[];
@ApiPropertyOptional({ enum: TrueOrFalseEnum })
getUsers?: TrueOrFalseEnum;
@ApiPropertyOptional({ enum: CanUntoggleLogicEnum })
canUntoggleLogic?: CanUntoggleLogicEnum;
}
export class GetRolesByCustomerRes {
+2 -2
View File
@@ -71,12 +71,12 @@ export class RolesController {
@ApiOkResponse({ type: GetRolesByCustomerRes })
async searchRoles(
@User() user: RequestUser,
@Query() filters: GetRolesByCustomerReq,
@Query() params: GetRolesByCustomerReq,
@Headers('dadosfera-lang') language,
) {
this.logger.info('searchRoles', { user });
this.rolesService.setLanguage(language);
return await this.rolesService.roleSearch(filters, user);
return await this.rolesService.roleSearch(params, user);
}
@Post()
+8 -15
View File
@@ -70,12 +70,7 @@ export class RolesService {
return { role: roleTreated };
}
async roleSearch(
filters: GetRolesByCustomerReq,
{ customer_id, access_token }: any,
) {
const meta = new Metadata();
meta.add('access_token', access_token);
async roleSearch(filters: GetRolesByCustomerReq, { customer_id }) {
let { permissionId: permissionIds } = filters;
if (permissionIds && !Array.isArray(permissionIds))
permissionIds = [permissionIds];
@@ -89,15 +84,13 @@ export class RolesService {
return seqId;
}) || [];
const roles = await lastValueFrom(
this.rolesClientService.RoleSearch(
{
customerId: customer_id,
getUsers: isGetUsers,
permissionIds: [],
permissionSeqIds,
},
meta,
),
this.rolesClientService.RoleSearch({
customerId: customer_id,
getUsers: isGetUsers,
permissionIds: [],
permissionSeqIds,
canUntoggleLogic: filters.canUntoggleLogic,
}),
);
const rolesTreated = this.getRolesPermissionsName(roles.roles);
return { roles: rolesTreated };
@@ -6,6 +6,8 @@ import {
import { credentials } from '@grpc/grpc-js';
import { Transformation } from '@dadosfera/protospack-v2';
const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
export class TransformationsClientConfiguration {
public name = 'TransformationsClientConfiguration';
private config: GrpcOptions = {
@@ -13,10 +15,7 @@ export class TransformationsClientConfiguration {
options: {
url: process.env.INFACTORY_URL,
package: [Transformation.ProtoPackages.WritePackage],
credentials:
process.env.LOCAL_ENV || process.env.ENV === 'local'
? undefined
: credentials.createSsl(),
credentials: isLocalConnection ? undefined : credentials.createSsl(),
protoPath: [Transformation.ProtoPaths.WriteFilePath],
loader: {
enums: String,
+18 -13
View File
@@ -48,11 +48,10 @@ import {
IUserByCustomer,
} from './dtos/entities';
import { UsersService } from './users.service';
import { Metadata } from '@grpc/grpc-js';
import ErrorBuilder from 'src/utils/ErrorBuilder';
import ErrorCodes from 'src/utils/errorCodes';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
// TODO GET de hierarquias e do PATCH em usuário
@ApiTags('Users')
@Controller('users')
@ApiHeader({
@@ -75,10 +74,6 @@ export class UsersController {
}
@Get()
@ApiOkResponse({
type: GetAllUsersByCustomerIdRes,
description: 'List of users of a customer',
})
async getAllUsersByCustomerId(
@User() user: RequestUser,
@Headers('dadosfera-lang') language,
@@ -154,12 +149,14 @@ export class UsersController {
@Body() body: CreateUserReq,
@Headers('dadosfera-lang') language,
) {
const meta = new Metadata();
meta.add('access_token', user.access_token);
const metadata = PackTheMetadata({
access_token: user.access_token,
language,
});
this.logger.info('createUser', { user });
this.userService.setLanguage(language);
return await this.userService.createUser(body, meta);
return await this.userService.createUser(body, metadata);
}
@Post('batch')
@@ -170,11 +167,15 @@ export class UsersController {
@Body() body: BatchCreateUserReq,
@Headers('dadosfera-lang') language,
) {
const meta = new Metadata();
meta.add('access_token', user.access_token);
this.logger.info('batchCreateUser', { user });
this.userService.setLanguage(language);
return await this.userService.batchCreateUser(body, meta);
const metadata = PackTheMetadata({
access_token: user.access_token,
language,
});
return await this.userService.batchCreateUser(body, metadata);
}
@Post(':id/resend-invite')
@@ -187,7 +188,11 @@ export class UsersController {
) {
this.logger.info('resendInvite', { user });
this.userService.setLanguage(language);
return this.userService.resendInvite({ id });
const metadata = PackTheMetadata({
language,
});
return this.userService.resendInvite({ id }, metadata);
}
@Delete('role')
+6 -2
View File
@@ -122,6 +122,7 @@ export class UsersService implements OnModuleInit {
if (roleNames) {
await this.setRoles({ roleNames, userId: id }, customerId);
}
const { user } = await lastValueFrom(
this.usersClientService.UserUpdate({
name,
@@ -131,6 +132,7 @@ export class UsersService implements OnModuleInit {
hierarchy,
jobTitle,
email,
metabaseUserId: undefined,
}),
);
return { user: this.adjustUsersPayload([user])[0] };
@@ -183,8 +185,10 @@ export class UsersService implements OnModuleInit {
};
}
async resendInvite(body: IdRequest) {
return lastValueFrom(this.usersClientService.UserResendInvite(body));
async resendInvite(body: IdRequest, metadata: Metadata) {
return lastValueFrom(
this.usersClientService.UserResendInvite(body, metadata),
);
}
async assignRoleToUser(body: AssignRoleToUserRequest) {
+2
View File
@@ -7,8 +7,10 @@ interface IMetadata {
user_id?: string;
language?: string;
details?: string;
sensitive?: string;
roles?: string[];
is_data_manager?: boolean;
access_token?: string;
}
export function PackTheMetadata(info: IMetadata): Metadata {
+18
View File
@@ -214,6 +214,16 @@ export function EnrichErrorCode(code: string) {
message: 'Id encontra-se num formato inválido',
code,
};
case ErrorCodes.CONNECTION.NOT_FOUND:
case ErrorCodes.CONNECTION.PLATFORM_NOT_FOUND:
return {
statusCode: HttpStatus.NOT_FOUND,
error: 'Conexão não encontrada',
message:
'Não encontramos a conexão solicitada. Caso o erro persista, entre em contato com o suporte',
code,
};
case ErrorCodes.CONNECTION_TEST.ERROR:
return {
statusCode: HttpStatus.BAD_REQUEST,
@@ -282,6 +292,14 @@ export function EnrichErrorCode(code: string) {
message: 'Caso o erro persista, entre em contato com o suporte',
code,
};
case ErrorCodes.CATALOG.DATA_ASSET_NOT_FOUND:
return {
statusCode: HttpStatus.NOT_FOUND,
error: 'Não encontramos o ativo solicitado',
message:
'Tente realizar a ação novamente. Caso o erro persista, entre em contato com o suporte',
code,
};
case ErrorCodes.INTERNAL:
case ErrorCodes.UNKNOWN:
default:
+4
View File
@@ -2,6 +2,7 @@ import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
let cachedSecrets: OauthSecrets;
class OauthSecretsObject {
client_id = '';
@@ -15,8 +16,10 @@ export class OauthSecrets {
mailchimp = new OauthSecretsObject();
facebook = new OauthSecretsObject();
salesforce = new OauthSecretsObject();
'google-login' = new OauthSecretsObject();
}
export async function getOauthSecrets() {
if (cachedSecrets) return cachedSecrets;
const secrets = new OauthSecrets();
const path = process.env.SM_OAUTH_PATH;
const secretsManagerClient = new SecretsManagerClient({});
@@ -37,5 +40,6 @@ export async function getOauthSecrets() {
};
}
}
cachedSecrets = secrets;
return secrets;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import {
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
export async function getSecreteFromSecreteManager(path: string) {
export async function getSecretFromSecretsManager(path: string) {
const secretsManagerClient = new SecretsManagerClient({});
const getSecretComand = new GetSecretValueCommand({
+43
View File
@@ -60,9 +60,16 @@ const PIPELINE = {
const INPUT = {
NOT_FOUND: 'INPUT.NOT_FOUND',
};
const CONNECTION = {
NOT_FOUND: 'CONNECTION.NOT_FOUND',
PLATFORM_NOT_FOUND: 'CONNECTION.PLATFORM_NOT_FOUND',
};
const TRANSFORMATION = {
NOT_FOUND: 'TRANSFORMATION.NOT_FOUND',
};
const CATALOG = {
DATA_ASSET_NOT_FOUND: 'CATALOG.DATA_ASSET_NOT_FOUND',
};
const ErrorCodes = {
UNKNOWN: 'UNKNOWN',
RATE_LIMIT: 'RATE_LIMIT',
@@ -77,6 +84,42 @@ const ErrorCodes = {
PIPELINE,
INPUT,
TRANSFORMATION,
CONNECTION,
CATALOG,
};
export default ErrorCodes;
export const OauthErrors = {
INVALID_CREDENTIALS: {
'pt-br': {
error: 'Erro ao autorizar',
error_description: 'Credenciais inválidas. Tente novamente',
},
'en-us': {
error: 'Authorization Error',
error_description: 'Invalid credentials. Please try again',
},
},
USER_NOT_FOUND: {
'pt-br': {
error: 'Erro ao autorizar',
error_description: (email) => `Usuário não existe na Dadosfera: ${email}`,
},
'en-us': {
error: 'Authorization Error',
error_description: (email) =>
`User does not exist on Dadosfera: ${email}`,
},
},
INVALID_SESSION: {
'pt-br': {
error: 'Erro ao autorizar',
error_description: 'Sessão inválida!',
},
'en-us': {
error: 'Authorization Error',
error_description: 'Invalid session!',
},
},
};