Compare commits

...
150 Commits
Author SHA1 Message Date
Gabriel Amorim dd4a1ecd46 FEAT: passport+connector manager
Merge pull request #110 from dadosfera/feat/passport+connector-manager
2022-06-28 19:09:40 -03:00
Gabriel Rosa 6eaaa746d0 FIX: merge conflict fixed 2022-06-28 18:40:52 -03:00
Gabriel Rosa df79fbdab0 Merge branch 'merge/auth+connectors-manager' of github.com:dadosfera/maestro into merge/auth+connectors-manager 2022-06-28 18:37:44 -03:00
Gabriel Rosa 0f34152b02 Merge branch 'feat/passport+connector-manager' of github.com:dadosfera/maestro into feat/passport+connector-manager 2022-06-28 18:36:49 -03:00
Gabriel Rosa 08d576432e FIX: new permissions 2022-06-28 18:36:41 -03:00
arthur simas ce0362217c Merge pull request #109 from dadosfera/feat/new-permissions
FEAT: new orchest permissions
2022-06-28 18:31:03 -03:00
Arthur Simas 3399f125e5 Merge branch 'feat/new-permissions' into feat/passport+connector-manager 2022-06-28 18:02:53 -03:00
Arthur Simas 9e0a58b4b7 FEAT: new orchest permissions 2022-06-28 18:02:01 -03:00
Gabriel Rosa 49a9c93d6f merge main 2022-06-28 15:24:21 -03:00
Gabriel Rosa ddb81d3082 REFACTOR: removed unused imports 2022-06-28 12:31:56 -03:00
Gabriel Rosa 9d9d903335 FIX: Better error handling on pipeline create 2022-06-28 12:22:00 -03:00
arthur simas 3ddbfcebeb FEAT: new orchest permission + added missing field user.createdAt on signin payload (#108) 2022-06-28 11:52:53 -03:00
Gabriel Rosa e1d0aa00f5 swagfer changed 2022-06-28 10:24:54 -03:00
Gabriel Rosa 4209b5b23e merge with connector manager 2022-06-28 09:26:45 -03:00
Arthur Simas d6607b1d9f FEAT: connector manager 2022-06-27 19:22:36 -03:00
Arthur Simas 0eef0713df FIX: logging errors only if auth is required 2022-06-27 18:32:08 -03:00
Arthur Simas 17d629b430 FIX: suppressing health check not authenticated log 2022-06-27 18:32:08 -03:00
Arthur Simas 5362a99b24 CHORE: added protospack@v2 correct version 2022-06-27 18:32:08 -03:00
Arthur Simas 1566efc490 FEAT: squash - auth guard
nestjs access controll mock

swagger implementation

middleware changes and lib

permissions file back to maestro

FEAT: sending permissions to duc on startup


FEAT: new permissions.enum format


FEAT: permissions injection (maestro -> duc)


FIX: changing method invocation order


FEAT: refactored permission enum + duc permission injector + grpcHandler


FEAT: auth guards


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

swagger implementation

middleware changes and lib

permissions file back to maestro

FEAT: sending permissions to duc on startup


FEAT: new permissions.enum format


FEAT: permissions injection (maestro -> duc)


FIX: changing method invocation order


FEAT: refactored permission enum + duc permission injector + grpcHandler


FEAT: auth guards


FEAT: seq ids updated
2022-06-23 22:02:49 -03:00
Gabriel Amorim 89b571d7f8 FIX: Use passport for oauth
Merge pull request #105 from dadosfera/feature/passport-oauth
2022-06-23 18:44:25 -03:00
Gabriel Rosa 7776d61f82 fix frontend redirect url 2022-06-23 17:02:33 -03:00
Victor Radael 0e2f935807 Add insomnia file att 2022-06-23 16:59:08 -03:00
Gabriel Rosa 9bf7cc660e tiny code organization improvements 2022-06-23 16:31:48 -03:00
Gabriel Rosa d6552678a8 get credentials from AWS secrets manager 2022-06-23 16:26:49 -03:00
Victor Radael 9e25a1ffa7 Add change body to query params 2022-06-23 16:09:45 -03:00
Victor Radael 44ceee0671 Add change body to query params 2022-06-23 16:08:17 -03:00
Gabriel Rosa a42ec164c3 Merge branch 'fix/oauth-generic' into feature/passport-oauth 2022-06-23 11:43:43 -03:00
Gabriel Rosa 6abc2595cb get oauthSecrets from secrets manager 2022-06-23 11:34:12 -03:00
Gabriel Rosa b850596d64 get oauth secrets from secrets manager 2022-06-22 17:51:30 -03:00
Gabriel Rosa da3b7f3096 hubspot oauth 2022-06-22 15:24:36 -03:00
Gabriel Rosa ff2b8fae6f generic axios 2022-06-22 09:49:23 -03:00
Victor Radael 44337dc4ed Add Insomnia Folder 2022-06-21 18:05:56 -03:00
Victor Radael 80e7aa7b9c Fix and add upload file 2022-06-21 18:01:21 -03:00
Victor Radael 6f4bb9ce6d Fix and add upload file 2022-06-21 18:01:06 -03:00
Gabriel Amorim 4df23c917a FEAT: new connectors
Merge pull request #103 from dadosfera/feature/hubspot-connector
2022-06-21 11:44:08 -03:00
Victor Radael 92afbb5e55 Find All 2022-06-21 10:23:14 -03:00
Gabriel Rosa 6c3eef7181 update protospack and deploy file 2022-06-21 10:13:20 -03:00
Gabriel Rosa 072a9fee8e hubspot guard 2022-06-20 17:09:28 -03:00
Victor Radael 953c0ac41e Register Connector 2022-06-20 15:37:45 -03:00
Gabriel Rosa 8c91551cdc incomplete oauth 2022-06-20 15:07:23 -03:00
Gabriel Rosa 668c5fe6a5 WIP: Documentation of input API 2022-06-20 11:36:26 -03:00
Gabriel Rosa d057495b2e FIX: redirect_uri.redirect_uri fixed 2022-06-15 15:19:45 -03:00
Gabriel Rosa 2f1c71eefe update protospack and better adjustInputPayload 2022-06-15 11:14:22 -03:00
Gabriel Rosa 3bb014e714 get redirect_uri from get_tokens_url_params 2022-06-15 10:08:03 -03:00
Gabriel Rosa 2ff2aa049f update protospack 2022-06-15 08:52:30 -03:00
Gabriel Rosa f6c7420f59 finding inputs using generic strategy 2022-06-15 08:44:21 -03:00
Gabriel Rosa abbe88f45b removed unused statement 2022-06-14 14:02:06 -03:00
Gabriel Rosa 383741f48c get entities 2022-06-14 14:01:40 -03:00
Gabriel Rosa ff663e7166 update protospack 2022-06-14 11:35:09 -03:00
Gabriel Rosa 305daeeeab update protospack 2022-06-13 16:50:38 -03:00
Gabriel Rosa 852ddbf88b transforming to camelCase before sending to in-factory 2022-06-13 15:55:02 -03:00
Gabriel Rosa 4dfe6c3870 fallback for env vars 2022-06-13 13:12:01 -03:00
Gabriel Rosa 2710159a73 minor improvements 2022-06-13 11:17:41 -03:00
Gabriel Rosa 1ab5d49908 removing info that won't be saved on database 2022-06-13 10:49:27 -03:00
Gabriel Amorim 43372b5a0f FIX: Better error handling on input creation
Merge pull request #102 from dadosfera/fix/error-handling
2022-06-10 16:05:29 -03:00
Gabriel Rosa 0ac2dd2de7 Better error handling on input creation 2022-06-10 15:57:19 -03:00
Gabriel Rosa 0342386a76 oauth almost complete 2022-06-09 17:14:11 -03:00
Gabriel Rosa 28e55f8fd9 update node version 2022-06-08 11:12:51 -03:00
Gabriel Rosa 4e2e296442 Fixed cron on create S3 2022-06-08 10:31:59 -03:00
Arthur Simas d89573bc79 FIX: creating swap file 2022-06-06 17:35:26 -03:00
Gabriel Amorim 03ed4d88a0 FIX: validate cron
Merge pull request #101 from dadosfera/fix/validate-cron
2022-05-31 16:33:05 -03:00
Gabriel Rosa 329bbf2e4d Better cron validation 2022-05-31 16:10:21 -03:00
Gabriel Rosa ae069a0e00 removed unused imports 2022-05-31 12:04:32 -03:00
Gabriel Rosa d3bcbdc43c validating cron when creating or updating input 2022-05-31 11:34:28 -03:00
Arthur Simas 4e2e6787a4 Merge branch 'feat/change-password' into main 2022-05-30 18:27:02 -03:00
Arthur Simas 50da33b766 FEAT: added verifyPasswordResetCode endpoint 2022-05-30 17:17:50 -03:00
Rodrigo Zamboni bb72cfce45 Merge pull request #100 from dadosfera/tests
Tests
2022-05-30 10:57:23 -03:00
rodrigo.zamboni 03d17758ae FIX: commented broken test 2022-05-30 10:11:04 -03:00
rodrigo.zamboni b7e76a6d3f FIX: fix busboy npm critical error 2022-05-30 09:58:07 -03:00
Arthur Simas c920eb27b9 FEAT: reset password 2022-05-27 18:57:05 -03:00
Arthur Simas c25d2427c2 FEAT: change password 2022-05-27 18:55:43 -03:00
Arthur Simas 0f13d0c590 FIX(refresh token): removed username param 2022-05-27 18:55:35 -03:00
Arthur Simas 7781298e6e Merge branch 'hotfix/refresh-token-contract' into fix/create-partial-input 2022-05-27 15:25:00 -03:00
Arthur Simas 2c00d3249a FIX(refresh token): removed username param 2022-05-27 15:12:22 -03:00
Arthur Simas cd7c7d538f CI: deploy workflow updated 2022-05-26 15:38:36 -03:00
rodrigo.zamboni e496d42776 ódio 2022-05-26 10:13:42 -03:00
Gabriel Rosa a07247d538 update protospack version 2022-05-25 15:49:45 -03:00
Arthur Simas 153ba1a98b CI: printing env 2022-05-25 12:24:50 -03:00
Gabriel Rosa 46519323f8 Ci: imporved github flow 2022-05-25 11:56:26 -03:00
Arthur Simas a51d52d68e CI: fixed versioning 2022-05-25 11:53:44 -03:00
Gabriel Rosa 555ff2eda9 CI - github flow melhorado 2022-05-25 11:52:54 -03:00
Arthur Simas 5d1a54a720 CI: . 2022-05-25 11:45:37 -03:00
Arthur Simas 8399a0e4c1 CI: . 2022-05-25 11:40:38 -03:00
Arthur Simas cf54cb0bc2 CI: not considering semver during manual deploy 2022-05-25 11:37:14 -03:00
rodrigo.zamboni 4eba099390 Merge branch 'main' of https://github.com/dadosfera/maestro into tests 2022-05-25 11:14:45 -03:00
rodrigo.zamboni 08fa269ce0 initial tests 2022-05-25 11:14:40 -03:00
Gabriel Rosa e52af6844b FIX: create partial input 2022-05-25 11:09:24 -03:00
Arthur Simas 2344e4dba3 CI: using github-flow deployment workflow 2022-05-24 18:57:41 -03:00
rodrigo.zamboni d686753f25 DOCS: Added proper readme 2022-05-23 17:19:36 -03:00
Rodrigo Zamboni c1498f8f1b Merge pull request #98 from dadosfera/beta
Beta -> main
2022-05-20 17:27:29 -03:00
Gabriel Rosa 0b3cb581c4 merge alpha -> beta 2022-05-20 15:19:43 -03:00
Gabriel Rosa 97a6e05812 FIX: returning input when creating jdbc 2022-05-20 15:19:00 -03:00
Gabriel Rosa 356d438404 merge alpha 2022-05-20 14:07:02 -03:00
Gabriel Rosa cf870cff14 FIX: formating payload 2022-05-20 14:06:13 -03:00
Gabriel Rosa 26c6a289eb merge from beta 2022-05-20 11:51:48 -03:00
Gabriel Rosa 8d27e42fcd FIX: protospack version 2022-05-20 11:51:08 -03:00
Gabriel Rosa c44e557b11 merge beta 2022-05-20 10:08:20 -03:00
Gabriel Rosa 25d06ab4c5 FIX: send input to createS3 2022-05-20 10:07:38 -03:00
Gabriel Amorim 31bb795d23 Merge pull request #97 from dadosfera/alpha
Alpha -> Beta
2022-05-20 09:12:34 -03:00
Gabriel Rosa a2e25eb737 FIX: added ssl back 2022-05-20 08:51:25 -03:00
Gabriel Rosa 6be53bebaf FIX: adjusting pipeline payload fixed 2022-05-20 08:50:08 -03:00
Rodrigo Zamboni 741f009723 Merge pull request #96 from dadosfera/alpha
Alpha
2022-05-19 15:20:06 -03:00
Rodrigo Zamboni 45477b5750 Merge branch 'beta' into alpha 2022-05-19 15:09:15 -03:00
rodrigo.zamboni 36de53dfd3 FIX: lib version 2022-05-19 14:48:22 -03:00
rodrigo.zamboni cd8fe2fbf6 CI: Deploy 2022-05-19 14:46:17 -03:00
rodrigo.zamboni c4a2bee69c CI: DEploy 2022-05-19 14:18:08 -03:00
rodrigo.zamboni e48d11153d FIX: Added S3 or JDBC input selection for pipelines 2022-05-19 14:13:06 -03:00
Gabriel Amorim c7e4de7d65 Merge pull request #95 from dadosfera/feat/mfa
FEAT: MFA finished + better error handling
2022-05-19 13:59:46 -03:00
Arthur Simas b2e7870cc5 CHORE: protospack version bump 2022-05-19 12:56:32 -03:00
Arthur Simas 1e239026a3 FEAT: MFA finished + better error handling 2022-05-19 12:02:08 -03:00
Rodrigo Zamboni bc108d43e3 Merge pull request #94 from dadosfera/beta
Beta
2022-05-18 16:54:04 -03:00
Arthur Simas 1c5b929c1b REFACTOR: nimbus URL 2022-05-18 13:57:19 -03:00
rodrigo.zamboni 3e21c308f6 UPDATE: Added S3 Input Creation 2022-05-17 15:58:18 -03:00
Gabriel Rosa f4a5f91cbb FIX: remove backmerge 2022-05-16 17:03:12 -03:00
Gabriel Rosa c8aeb7f3db FIX: remove backmerge 2022-05-16 17:02:46 -03:00
Victor Radael b9453b11cf REFACTOR: Init Catalog Class 2022-05-16 14:02:18 -03:00
Victor Radael a69752332e FIX: Nimbus URL Strategy 2022-05-16 13:42:06 -03:00
Victor Radael 09c7cc79ee FIX: Nimbus URL Strategy 2022-05-16 12:04:13 -03:00
Arthur Simas 65f6f03e55 CHORE: pinning protospack version to 1.7.4 2022-05-13 11:15:05 -03:00
Arthur Simas a54300f600 CHORE: pinning protospack version to 1.7.2 2022-05-13 11:00:25 -03:00
Arthur Simas 5ef85f79d3 Merge branch 'alpha' into beta 2022-05-13 10:49:27 -03:00
rodrigo.zamboni a8a429977f UPDATE: Implementing MFA routes on maestro 2022-05-11 18:02:01 -03:00
Gabriel Amorim 692a9de0af FEAT: add refresh token - Merge pull request #90 from dadosfera/feat/refreshToken
FEATURE: add refresh token
2022-05-11 10:37:36 -03:00
rodrigo.zamboni 7dc669ca44 FIX: fix auth trim 2022-05-10 15:53:47 -03:00
rodrigo.zamboni e897da1ef0 FIX: Fix login middleware 2022-05-10 15:32:34 -03:00
Arthur Simas b366e786ba STYLE: styling conforming to ESLint 2022-05-10 10:23:55 -03:00
Arthur Simas 24d56c7a58 FEAT: add refresh token 2022-05-09 11:47:25 -03:00
rodrigo.zamboni d7f205901b UPDATE: Changing transformation factory microservice client url to inputs factory 2022-05-05 16:27:20 -03:00
rodrigo.zamboni 7212c5249c FIX: testing timeout on testconnection input client 2022-05-04 16:44:00 -03:00
rodrigo.zamboni 827c7b1bf1 FIX: Summary-rating returning default 0 values 2022-05-04 12:00:01 -03:00
rodrigo.zamboni 8d89b747b4 FIX: Fix delete routes method 2022-05-04 00:24:44 -03:00
rodrigo.zamboni 4f18328603 FIX: update protospack lib version 2022-05-03 17:59:51 -03:00
rodrigo.zamboni a68d312447 FIX: updated protospack lib 2022-05-03 17:07:57 -03:00
66 changed files with 15255 additions and 1635 deletions
+3
View File
@@ -0,0 +1,3 @@
container_commands:
01setup_swap:
command: "bash .ebextensions/setup_swap.sh"
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
SWAPFILE=/var/swapfile
SWAP_MEGABYTES=1024
if [ -f $SWAPFILE ]; then
echo "Swapfile $SWAPFILE found, assuming already setup"
exit;
fi
/bin/dd if=/dev/zero of=$SWAPFILE bs=1M count=$SWAP_MEGABYTES
/bin/chmod 600 $SWAPFILE
/sbin/mkswap $SWAPFILE
/sbin/swapon $SWAPFILE
@@ -2,117 +2,109 @@ name: Deploy
on:
push:
branches:
- alpha
- beta
- main
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
type: choice
options:
- dev
- stg
- prd
jobs:
extract_environment:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.extract_environment.outputs.environment }}
env:
EVENT: ${{ github.event_name }}
DEPLOY_ENV: ${{ github.event.inputs.environment }}
steps:
- name: Extract Environment
run: |
if [ ${GITHUB_REF} == "refs/heads/main" ]; then
if [ ${EVENT} == "workflow_dispatch" ]; then
echo "##[set-output name=environment;]$(echo ${DEPLOY_ENV})"
elif [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "##[set-output name=environment;]$(echo "prd")"
elif [ ${GITHUB_REF} == "refs/heads/alpha" ]; then
echo "##[set-output name=environment;]$(echo "dev")"
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "##[set-output name=environment;]$(echo "stg")"
fi
id: extract_environment
semantic_release:
runs-on: ubuntu-latest
outputs:
#new_release_published: ${{ steps.semantic.outputs.last_release_version != steps.semantic.outputs.new_release_version }}
new_release_published: ${{ steps.semantic.outputs.new_release_published }}
new_release_version: ${{ steps.semantic.outputs.new_release_version }}
#new_release_version: ${{ steps.semantic.outputs.new_release_version }}
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Semantic Release
- if: github.event_name != 'workflow_dispatch'
name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
id: semantic
with:
semantic_version: 16
extra_plugins: |
@saithodev/semantic-release-backmerge
conventional-changelog-eslint
branches: |
[
'main',
{
name: 'alpha',
prerelease: true
},
{
name: 'beta',
prerelease: true
}
'main'
]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy:
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
needs: [extract_environment, semantic_release]
runs-on: [self-hosted, "${{ needs.extract_environment.outputs.environment }}"]
runs-on:
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
steps:
- name: Printing stats
env:
EVENT: ${{ github.event_name }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ENV: ${{ needs.extract_environment.outputs.environment }}
run: echo ${GITHUB_REF#refs/heads/}
- name: Checkout
uses: actions/checkout@v2
- name: Update Pip
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install --upgrade pip
- name: Install Docker Compose
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install docker-compose --upgrade
- name: Install AWS CLI
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install awscli --upgrade
- name: Install AWS Elastic Beanstalk CLI
if: needs.semantic_release.outputs.new_release_published == 'true'
run: |
python3 -m pip install awsebcli --upgrade
- name: Configure AWS Region
if: needs.semantic_release.outputs.new_release_published == 'true'
uses: aws-actions/configure-aws-credentials@v1
id: aws
with:
aws-region: us-east-1
- name: Extract Environment
if: needs.semantic_release.outputs.new_release_published == 'true'
shell: bash
run: |
if [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "##[set-output name=result;]$(echo "prd")"
elif [ ${GITHUB_REF} == "refs/heads/alpha" ]; then
echo "##[set-output name=result;]$(echo "dev")"
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "##[set-output name=result;]$(echo "stg")"
fi
id: extract_environment
- name: Login to AWS ECR
if: needs.semantic_release.outputs.new_release_published == 'true'
id: login_ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, Tag, and Push Image to AWS ECR
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -121,9 +113,8 @@ jobs:
docker-compose -f build.docker-compose.yml push
- name: Create ZIP file to Deploy AWS Beanstalk
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: |
@@ -133,18 +124,17 @@ jobs:
zip deploy.zip docker-compose.yml -r .ebextensions
- name: Deploy AWS Beanstalk
if: needs.semantic_release.outputs.new_release_published == 'true'
env:
ENV: ${{ steps.extract_environment.outputs.result }}
ENV: ${{ needs.extract_environment.outputs.environment }}
AWS_REGION: us-east-1
APP_NAME: maestro
APP_NAME: ${{ github.event.repository.name }}
run: |
eb use $APP_NAME-$ENV
echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
eb deploy
- name: Remove Docker's Trash
continue-on-error: true
if: always()
run: |
docker system prune
docker rmi -f $(docker images -aq)
docker system prune --volumes -a -f
docker system df
+3 -3
View File
@@ -40,7 +40,7 @@ jobs:
docker-compose -f build.docker-compose.yml run -e NPM_TOKEN=${NPM_TOKEN} --rm --entrypoint="npm run test" maestro
- name: Remove Docker's Trash
continue-on-error: true
if: always()
run: |
docker system prune
docker rmi -f $(docker images -aq)
docker system prune --volumes -a -f
docker system df
+1 -1
View File
@@ -29,7 +29,7 @@ lerna-debug.log*
# IDE - VSCode
.vscode/*
!.vscode/settings.json
#!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+1
View File
@@ -0,0 +1 @@
18.3.0
+6 -18
View File
@@ -5,18 +5,18 @@
{
"preset": "eslint",
"releaseRules": [
{ "tag": "FIX", "release": "patch" },
{ "tag": "BREAKING", "release": "major" },
{ "tag": "FEAT", "release": "minor" },
{ "tag": "UPDATE", "release": "minor" },
{ "tag": "UPGRADE", "release": "minor" },
{ "tag": "BREAKING", "release": "major" },
{ "tag": "DOCS", "release": "patch" },
{ "tag": "CHORE", "release": "minor" },
{ "tag": "BUILD", "release": "patch" },
{ "tag": "REFACTOR", "release": "minor" },
{ "tag": "PERF", "release": "minor" },
{ "tag": "FIX", "release": "patch" },
{ "tag": "DOCS", "release": "patch" },
{ "tag": "BUILD", "release": "patch" },
{ "tag": "TEST", "release": "patch" },
{ "tag": "CI", "release": "patch" },
{ "tag": "PERF", "release": "minor" }
{ "tag": "CI", "release": "patch" }
]
}
],
@@ -26,18 +26,6 @@
"preset": "eslint"
}
],
[
"@saithodev/semantic-release-backmerge",
{
"branches": [
{ "from": "main", "to": "alpha" },
{ "from": "main", "to": "beta" }
],
"backmergeStrategy": "merge",
"clearWorkspace": true,
"restoreWorkspace": true
}
],
"@semantic-release/npm",
"@semantic-release/github"
]
+2 -1
View File
@@ -1,7 +1,8 @@
FROM node:14.15.4-alpine3.12 as packages
FROM node:18.3.0-alpine3.15 as packages
WORKDIR /packages
ARG NPM_TOKEN
COPY package.json .
COPY package-lock.json .
COPY .npmrc .
RUN npm install
RUN rm -f ./.npmrc
Binary file not shown.
+48 -2
View File
@@ -1,4 +1,50 @@
# Maestro
<p align="center">
<image src="./assets/maestro.svg" style="width:10rem">
<h1 align="center">Maestro</h1>
</p>
<h1 align="center">Maestro</h1>
</p>
This is the Dadosfera´s gateway repository, it´s responsable for the communication between frontend application and Dadosfera´s mirosservices.
## 💻 Requirements
Before you start, make sure you have done the following steps:
* Installed Nodejs version 16.14.2
* Installed latest NPM version
## 🚀 Installing Maestro
First of all clone the repository:
* SSH:
```
git clone git@github.com:dadosfera/maestro.git
```
* HTTPS:
```
git clone git@github.com:dadosfera/maestro.git
```
## Enviroment variables
Here is a list of enviroment variables needed in order to run the application correctly.
```
ENV=
DUC_URL=
INFACTORY_URL=
TRFACTORY_URL=
OTFACTORY_URL=
PIFACTORY_URL=
JWT_PRIVATE_KEY=
AWS_IDENTITY_POOL_ID=
```
## Running Maestro
In order to run Maestro just run the following command:
```
npm run start:dev
```
If everything is fine the Maestro will start and be ready to receive HTTP requests
+7 -2
View File
@@ -2,7 +2,12 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": ["**/*.proto"],
"assets": [
"**/*.proto"
],
"plugins": [
"@nestjs/swagger"
],
"watchAssets": true
}
}
}
+12284 -775
View File
File diff suppressed because it is too large Load Diff
+44 -23
View File
@@ -1,10 +1,13 @@
{
"name": "grpc-server-and-client-nestjs-micro-pattern",
"name": "maestro",
"version": "0.0.0",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"engines": {
"node": "18.3.0"
},
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
@@ -21,48 +24,66 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@grpc/grpc-js": "^1.5.10",
"@grpc/proto-loader": "^0.6.9",
"@nestjs/common": "^8.4.3",
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/common": "^8.4.7",
"@nestjs/config": "^1.2.1",
"@nestjs/core": "^8.4.3",
"@nestjs/core": "^8.4.7",
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^8.4.3",
"@nestjs/platform-express": "^8.4.3",
"@nestjs/microservices": "^8.4.7",
"@nestjs/passport": "^8.2.2",
"@nestjs/platform-express": "^8.4.7",
"@nestjs/schedule": "^1.1.0",
"@nestjs/swagger": "^5.2.1",
"@victorradael/protospack": "^1.6.1",
"@victorradael/protospack": "2.5.0",
"@victorradael/protospack-v2": "1.1.1",
"axios": "^0.25.0",
"dotenv": "^14.2.0",
"helmet": "^5.0.2",
"cron-parser": "^4.4.0",
"dotenv": "^14.3.2",
"helmet": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"jwk-to-pem": "^2.0.5",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-google-oauth20": "^2.0.0",
"passport-hubspot-oauth2": "^1.0.3",
"passport-mailchimp": "^1.1.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"swagger-ui-express": "^4.3.0"
"swagger-ui-express": "^4.4.0"
},
"overrides": {
"multer": "1.4.5-lts.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.4",
"@nestjs/schematics": "^8.0.8",
"@nestjs/testing": "^8.4.3",
"@nestjs/cli": "^8.2.8",
"@nestjs/schematics": "^8.0.11",
"@nestjs/testing": "^8.4.7",
"@types/express": "^4.17.13",
"@types/jest": "27.0.2",
"@types/jsonwebtoken": "^8.5.8",
"@types/jwk-to-pem": "^2.0.1",
"@types/node": "^16.11.26",
"@types/node": "^16.11.41",
"@types/passport-facebook": "^2.1.11",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-oauth2": "^1.4.11",
"@types/multer": "^1.4.7",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"eslint": "^8.12.0",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"prettier": "^2.6.1",
"nock": "^13.2.7",
"prettier": "^2.7.1",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.7",
"ts-node": "^10.7.0",
"supertest": "^6.2.3",
"ts-jest": "^27.1.5",
"ts-loader": "^9.3.1",
"ts-node": "^10.8.1",
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3"
},
+41 -24
View File
@@ -1,9 +1,7 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { ClientsModule } from '@nestjs/microservices';
import { ConfigModule } from '@nestjs/config';
import { LoggerMiddleware } from './middlewares/authentication';
import { InputsController } from './modules/inputs/inputs.controller';
import { TransformationsController } from './modules/transformations/transformations.controller';
import { OutputsController } from './modules/outputs/outputs.controllers';
@@ -15,28 +13,40 @@ import { InputsService } from './modules/inputs/inputs.service';
import { TransformationsService } from './modules/transformations/transformations.service';
import { OutputsService } from './modules/outputs/outputs.service';
import { PipelinesService } from './modules/pipelines/pipelines.service';
// import { AuthService } from './modules/auth/auth.service';
import { HealthService } from './modules/health/health.service';
import { AuthClientService } from './clients/auth/client.service';
import { InputsClientService } from './clients/inputs/client.service';
import { TransformationsClientService } from './clients/transformations/client.service';
import { OutputsClientService } from './clients/outputs/client.service';
import { PipelinesClientService } from './clients/pipelines/client.service';
import { AuthClientService } from './clients/auth/client.service';
import { PermissionsClientService } from './clients/permissions/client.service';
import { OutputsClientConfiguration } from './clients/outputs/client.config';
import { TransformationsClientConfiguration } from './clients/transformations/client.config';
import { AuthClient } from './clients/auth/client.config';
import { DucClient } from './clients/duc/client.config';
import { InputsClientConfiguration } from './clients/inputs/client.config';
import { PipelinesClientConfiguration } from './clients/pipelines/client.config';
import { CatalogController } from './modules/catalog/catalog.controller';
import { CatalogService } from './modules/catalog/catalog.service';
import { OauthController } from './modules/oauth/oauth.controller';
import { getOauthSecrets } from './utils/OauthSecrets';
import { HubspotStrategy } from './modules/oauth/passport-strategies/hubspot-strategy';
import { FacebookStrategy } from './modules/oauth/passport-strategies/facebook-strategy';
import { AuthenticationGuard } from './authentication/authentication.guard';
import { APP_GUARD } from '@nestjs/core';
import { GoogleStrategy } from './modules/oauth/passport-strategies/google-strategy';
import { MailChimpStrategy } from './modules/oauth/passport-strategies/mailchimp-strategy';
import { ConnectorClientConfiguration } from './clients/connector/client.config';
import { ConnectorController } from './modules/connector/connector.controller';
import { ConnectorClientService } from './clients/connector/client.service';
const authClient = new AuthClient();
const ducClient = new DucClient();
const inputClient = new InputsClientConfiguration();
const outputClient = new OutputsClientConfiguration();
const pipelineClient = new PipelinesClientConfiguration();
const transformationClient = new TransformationsClientConfiguration();
const connectorClient = new ConnectorClientConfiguration();
@Module({
controllers: [
@@ -47,20 +57,36 @@ const transformationClient = new TransformationsClientConfiguration();
AuthController,
HealthController,
CatalogController,
OauthController,
ConnectorController,
],
providers: [
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
InputsService,
TransformationsService,
OutputsService,
PipelinesService,
// AuthService,
ConnectorClientService,
HealthService,
InputsClientService,
TransformationsClientService,
OutputsClientService,
PipelinesClientService,
AuthClientService,
PermissionsClientService,
CatalogService,
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
HubspotStrategy,
FacebookStrategy,
GoogleStrategy,
MailChimpStrategy,
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
],
imports: [
ConfigModule.forRoot({
@@ -72,7 +98,6 @@ const transformationClient = new TransformationsClientConfiguration();
name: 'INPUTS_PACKAGE',
...inputClient.config(),
},
{
name: 'TRANSFORMATIONS_PACKAGE',
...transformationClient.config(),
@@ -82,26 +107,18 @@ const transformationClient = new TransformationsClientConfiguration();
...outputClient.config(),
},
{
name: 'AUTH_PACKAGE',
...authClient.config(),
name: 'DUC_PACKAGE',
...ducClient.config(),
},
{
name: 'PIPELINES_PACKAGE',
...pipelineClient.config(),
},
{
name: 'CONNECTOR_PACKAGE',
...connectorClient.config(),
},
]),
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(
InputsController,
TransformationsController,
OutputsController,
PipelinesController,
CatalogController,
);
}
}
export class AppModule {}
@@ -0,0 +1,28 @@
import { SetMetadata, applyDecorators } from '@nestjs/common';
import { Request } from 'express';
import { Permission } from './permissions.enum';
export const PERMISSIONS_KEY = '__PERMISSIONS__';
export const MUST_BE_AUTHENTICATED_KEY = '__MUST_BE_AUTHENTICATED__';
export const CUSTOM_AUTHENTICATION_FUNCTION_KEY =
'__CUSTOM_AUTHENTICATION_FUNCTION__';
export type AuthenticationFunction = (req: Request, user: any) => boolean;
export function RequirePermissions(...permissions: Permission[]) {
return applyDecorators(
SetMetadata(PERMISSIONS_KEY, permissions),
SetMetadata(MUST_BE_AUTHENTICATED_KEY, true),
);
}
export function AuthenticateCondition(func: AuthenticationFunction) {
return applyDecorators(
SetMetadata(CUSTOM_AUTHENTICATION_FUNCTION_KEY, func),
SetMetadata(MUST_BE_AUTHENTICATED_KEY, true),
);
}
export function Authenticated() {
return SetMetadata(MUST_BE_AUTHENTICATED_KEY, true);
}
+162
View File
@@ -0,0 +1,162 @@
import {
Injectable,
CanActivate,
OnApplicationBootstrap,
ExecutionContext,
Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import assert from 'assert';
import jwt from 'jsonwebtoken';
import { AuthClientService } from '../clients/auth/client.service';
import { Permission } from './permissions.enum';
import {
AuthenticationFunction,
PERMISSIONS_KEY,
CUSTOM_AUTHENTICATION_FUNCTION_KEY,
MUST_BE_AUTHENTICATED_KEY,
} from './authentication.decorator';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
@Injectable()
export class AuthenticationGuard
implements CanActivate, OnApplicationBootstrap
{
private readonly logger = new Logger(AuthenticationGuard.name);
pems: Map<string, string>;
constructor(
private reflector: Reflector,
private authClient: AuthClientService,
) {
this.pems = new Map();
}
async onApplicationBootstrap() {
return this.loadDucJWKS();
}
async loadDucJWKS() {
const { keys } = await this.authClient.getPublicKeys();
keys.forEach((key) => {
this.pems.set(key.kid, key.pem);
});
}
canActivate(ctx: ExecutionContext): boolean {
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[ctx.getHandler(), ctx.getClass()],
);
const customAuthenticationFunction =
this.reflector.getAllAndOverride<AuthenticationFunction>(
CUSTOM_AUTHENTICATION_FUNCTION_KEY,
[ctx.getHandler(), ctx.getClass()],
);
const mustBeAuthenticated = this.reflector.getAllAndOverride<boolean>(
MUST_BE_AUTHENTICATED_KEY,
[ctx.getHandler(), ctx.getClass()],
);
const request = ctx.switchToHttp().getRequest();
const accessToken = this.verifyToken(request, mustBeAuthenticated);
if (!accessToken) {
// couldn't load valid token
if (!mustBeAuthenticated) {
// no need to be authenticated
return true;
}
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
}
if (
typeof customAuthenticationFunction === 'function' &&
!customAuthenticationFunction(request, request.user)
) {
// custom authentication function forbidden this request
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
}
if (
Array.isArray(requiredPermissions) &&
requiredPermissions.length > 0 &&
!this.matchPermissions(requiredPermissions, accessToken.permissions)
) {
// couldn't match permissions
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
}
return true;
}
private verifyToken(request, mustBeAuthenticated: boolean) {
const accessToken = request.get('Authorization');
let accessTokenPayload;
// If the user isn't authenticated, an error will occurr anywhere here.
// Fancy error avoidance isn't performed by purpose, such as avoiding to access null values.
try {
const accessTokenDecoded: any = jwt.decode(accessToken, {
complete: true,
});
/*assert(
accessTokenDecoded.payload.iss === 'duc',
'token should be issued by DUC',
);*/
assert(
accessTokenDecoded.payload.token_use === 'access',
'should be an access token',
);
const { kid } = accessTokenDecoded.header;
const pemValue: string = this.pems.get(kid);
jwt.verify(accessToken, pemValue);
accessTokenPayload = accessTokenDecoded.payload;
} catch (err) {
// log errors if authentication is required
if (mustBeAuthenticated) {
this.logger.log('Unable to verify duc access token\n' + err.stack);
}
return false;
}
request.accessTokenPayload = accessTokenPayload;
request.user = {
user_id: accessTokenPayload.user_id,
username: accessTokenPayload.username,
permissions: accessTokenPayload.permissions,
customer_id: accessTokenPayload.customer_id,
customer_name: accessTokenPayload.customer_name,
customer_tier: accessTokenPayload.customer_tier,
};
// TODO: for backwards compatibility. remove in the future
request.body.info = {
user_id: accessTokenPayload.user_id,
customer_id: accessTokenPayload.customer_id,
customer: accessTokenPayload.customer_name,
customer_tier: accessTokenPayload.customer_tier,
};
return accessTokenPayload;
}
private matchPermissions(
requiredPermissions: Permission[],
userPermissions: number[],
) {
return requiredPermissions.some((permission) =>
userPermissions.includes(permission.seqid),
);
}
}
+244
View File
@@ -0,0 +1,244 @@
import { Logger } from '@nestjs/common';
import assert from 'assert';
const logger = new Logger();
export enum PermissionUsages {
PUBLIC = 'public',
INTERNAL = 'internal',
}
export interface Permission {
seqid: number;
claim: string;
usage: PermissionUsages;
}
/*
- like field numbers in gRPC, avoid to change (or reuse previously used) seqids once its deployed to DUC
- it is possible to update claim and usage, not seqid
*/
/*export const Permissions: {
[P in keyof any]: { [Q in keyof any]: Permission };
} = {*/
export const Permissions = {
AUTH: {
CREATE: {
seqid: 18,
claim: 'POST /auth',
usage: PermissionUsages.PUBLIC,
},
},
PIPELINE: {
CREATE: {
seqid: 23,
claim: 'POST /pipelines',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 13,
claim: 'GET /pipelines',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 29,
claim: 'PUT /pipelines',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 5,
claim: 'DELETE /pipelines',
usage: PermissionUsages.PUBLIC,
},
},
INPUT: {
CREATE: {
seqid: 21,
claim: 'POST /inputs',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 9,
claim: 'GET /inputs',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 27,
claim: 'PUT /inputs',
usage: PermissionUsages.PUBLIC,
},
UPDATE_PARTIAL: {
seqid: 17,
claim: 'PATCH /inputs',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 3,
claim: 'DELETE /inputs',
usage: PermissionUsages.PUBLIC,
},
GET_ENTITIES: {
seqid: 10,
claim: 'GET /inputs/available-entities',
usage: PermissionUsages.PUBLIC,
},
},
OUTPUT: {
CREATE: {
seqid: 22,
claim: 'POST /outputs',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 12,
claim: 'GET /outputs',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 28,
claim: 'PUT /outputs',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 4,
claim: 'DELETE /outputs',
usage: PermissionUsages.PUBLIC,
},
},
TRANSFORMATIONS: {
CREATE: {
seqid: 24,
claim: 'POST /transformations',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 15,
claim: 'GET /transformations',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 30,
claim: 'PUT /transformations',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 6,
claim: 'DELETE /transformations',
usage: PermissionUsages.PUBLIC,
},
},
CATALOG: {
CREATE: {
seqid: 19,
claim: 'POST /catalog',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 7,
claim: 'GET /catalog',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 25,
claim: 'PUT /catalog',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 1,
claim: 'DELETE /catalog',
usage: PermissionUsages.PUBLIC,
},
},
CONNECTORS: {
CREATE: {
seqid: 20,
claim: 'POST /connectors',
usage: PermissionUsages.PUBLIC,
},
GET: {
seqid: 8,
claim: 'GET /connectors',
usage: PermissionUsages.PUBLIC,
},
UPDATE: {
seqid: 26,
claim: 'PUT /connectors',
usage: PermissionUsages.PUBLIC,
},
DELETE: {
seqid: 2,
claim: 'DELETE /connectors',
usage: PermissionUsages.PUBLIC,
},
},
SNOWFLAKE: {
OPEN: {
seqid: 14,
claim: 'GET /snowflake',
usage: PermissionUsages.PUBLIC,
},
},
ZENDESK: {
OPEN: {
seqid: 16,
claim: 'GET /zendesk',
usage: PermissionUsages.PUBLIC,
},
},
METABASE: {
OPEN: {
seqid: 11,
claim: 'GET /metabase',
usage: PermissionUsages.PUBLIC,
},
},
ORCHEST: {
INTELLIGENCE: {
seqid: 31,
claim: 'intelligence:open',
usage: PermissionUsages.PUBLIC,
},
APP_BUILDER: {
seqid: 32,
claim: 'app-builder:open',
usage: PermissionUsages.PUBLIC,
},
MACHINE_LEARNING: {
seqid: 33,
claim: 'machine-learning:open',
usage: PermissionUsages.PUBLIC,
},
},
};
// traverses the object searching for duplicate seqids or claims (executes at runtime)
let nextAvailableSeqid = 0;
const seqids = Object.values(Permissions).flatMap((namespace) =>
Object.values(namespace).map(({ seqid }) => seqid),
);
const claims = Object.values(Permissions).flatMap((namespace) =>
Object.values(namespace).map(({ claim }) => claim),
);
Object.values(Permissions).map((namespace) =>
Object.values(namespace).map(({ seqid, claim }) => {
const seqidsCount = seqids.filter((x) => x === seqid).length;
assert(seqidsCount === 1, `seqid ${seqid} count is not 1`);
const claimsCount = claims.filter((x) => x === claim).length;
assert(claimsCount === 1, `claim '${claim}' count is not 1`);
nextAvailableSeqid = Math.max(nextAvailableSeqid, seqid);
}),
);
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
+23
View File
@@ -0,0 +1,23 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
export interface RequestUser {
user_id: string;
username: string;
permissions: string;
customer_id: string;
customer_name: string;
customer_tier: string;
}
export const User = createParamDecorator((data: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if (!request.user && data?.required) {
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
}
return request.user;
});
+134 -30
View File
@@ -1,46 +1,150 @@
import { OnModuleInit, Inject } from '@nestjs/common';
import { Logger, OnModuleInit, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ProtoServices } from '@victorradael/protospack-v2/dist/lib/Duc';
import { AuthProtoService as AuthServiceInterface } from '@victorradael/protospack-v2/dist/lib/Duc/interfaces/write-service';
import {
AuthServiceInterface,
DucServicesNames,
} from '@victorradael/protospack';
AuthGetPublicKeysResponse,
AuthSignInRequest,
AuthSignInResponse,
AuthRefreshAccessTokenRequest,
AuthRefreshAccessTokenResponse,
AuthEnableTotpMfaRequest,
AuthEnableTotpMfaResponse,
AuthDisableTotpMfaRequest,
AuthDisableTotpMfaResponse,
AuthDismissTotpMfaRequest,
AuthDismissTotpMfaResponse,
AuthVerifyTotpMfaRequest,
AuthVerifyTotpMfaResponse,
AuthChangePasswordRequest,
AuthChangePasswordResponse,
AuthResetPasswordRequest,
AuthResetPasswordResponse,
AuthVerifyResetPasswordCodeRequest,
AuthVerifyResetPasswordCodeResponse,
AuthConfirmResetPasswordRequest,
AuthConfirmResetPasswordResponse,
} from '@victorradael/protospack-v2/dist/lib/Duc/interfaces/messages';
import { ILogin } from './interfaces';
import grpcHandler from '../../utils/grpcHandler';
export class AuthClientService implements OnModuleInit {
private readonly logger = new Logger(AuthClientService.name);
private authService: AuthServiceInterface;
constructor(
@Inject('AUTH_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
constructor(@Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc) {}
onModuleInit() {
this.authService = this.grpcClient.getService<AuthServiceInterface>(
DucServicesNames.AuthProtoService,
ProtoServices.AuthProtoService,
);
}
async signIn({ username, password }: ILogin): Promise<any> {
console.log('AuthClientService', 'SignIn');
async getPublicKeys() {
this.logger.log('GetPublicKeys');
const tokens = await new Promise((resolve, reject) => {
this.authService.signIn({ username, password }).subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
return tokens;
return grpcHandler<AuthGetPublicKeysResponse>(
this.authService.AuthGetPublicKeys({}),
);
}
async signIn({ username, password, totp }: AuthSignInRequest) {
this.logger.log('SignIn');
return grpcHandler<AuthSignInResponse>(
this.authService.AuthSignIn({ username, password, totp }),
);
}
async refreshAccessToken({ refreshToken }: AuthRefreshAccessTokenRequest) {
this.logger.log('RefreshAccessToken');
return grpcHandler<AuthRefreshAccessTokenResponse>(
this.authService.AuthRefreshAccessToken({ refreshToken }),
);
}
async changePassword({
accessToken,
oldPassword,
newPassword,
}: AuthChangePasswordRequest) {
this.logger.log('ChangePassword');
return grpcHandler<AuthChangePasswordResponse>(
this.authService.AuthChangePassword({
accessToken,
oldPassword,
newPassword,
}),
);
}
async resetPassword({ username }: AuthResetPasswordRequest) {
this.logger.log('resetPassword');
return grpcHandler<AuthResetPasswordResponse>(
this.authService.AuthResetPassword({ username }),
);
}
async verifyResetPasswordCode({
username,
code,
}: AuthVerifyResetPasswordCodeRequest) {
this.logger.log('verifyResetPasswordCode');
return grpcHandler<AuthVerifyResetPasswordCodeResponse>(
this.authService.AuthVerifyResetPasswordCode({ username, code }),
);
}
async confirmResetPassword({
username,
code,
newPassword,
}: AuthConfirmResetPasswordRequest) {
this.logger.log('confirmResetPassword');
return grpcHandler<AuthConfirmResetPasswordResponse>(
this.authService.AuthConfirmResetPassword({
username,
code,
newPassword,
}),
);
}
async enableTotpMFA({ accessToken, password }: AuthEnableTotpMfaRequest) {
this.logger.log('enableTotpMFA');
return grpcHandler<AuthEnableTotpMfaResponse>(
this.authService.AuthEnableTotpMfa({ accessToken, password }),
);
}
async disableTotpMFA({ accessToken, password }: AuthDisableTotpMfaRequest) {
this.logger.log('disableTotpMFA');
return grpcHandler<AuthDisableTotpMfaResponse>(
this.authService.AuthDisableTotpMfa({ accessToken, password }),
);
}
async dismissTotpMFA({ accessToken }: AuthDismissTotpMfaRequest) {
this.logger.log('dismissTotpMFA');
return grpcHandler<AuthDismissTotpMfaResponse>(
this.authService.AuthDismissTotpMfa({ accessToken }),
);
}
async verifyTotp({ accessToken, totp }: AuthVerifyTotpMfaRequest) {
this.logger.log('disableTotpMFA');
return grpcHandler<AuthVerifyTotpMfaResponse>(
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
);
}
}
-4
View File
@@ -1,4 +0,0 @@
export interface ILogin {
username: string;
password: string;
}
+31
View File
@@ -0,0 +1,31 @@
import { credentials } from '@grpc/grpc-js';
import { ClientOptions, Transport } from '@nestjs/microservices';
import { ConnectorManager } from '@victorradael/protospack-v2';
export class ConnectorClientConfiguration {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.INFACTORY_URL,
package: [
ConnectorManager.ProtoPackages.WritePackage,
ConnectorManager.ProtoPackages.ReadPackage,
],
credentials:
process.env.LOCAL_ENV === 'local'
? undefined
: credentials.createSsl(),
protoPath: [
ConnectorManager.ProtoPaths.WriteFilePath,
ConnectorManager.ProtoPaths.ReadFilePath,
],
loader: {
enums: String,
objects: true,
arrays: true,
},
},
};
}
}
+266
View File
@@ -0,0 +1,266 @@
import {
OnModuleInit,
Inject,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ConnectorManager } from '@victorradael/protospack-v2';
import { from } from 'rxjs';
export class ConnectorClientService implements OnModuleInit {
private connectorServiceRead: ConnectorManager.ReadService.ConnectorManagerReadServices;
private connectorServiceWrite: ConnectorManager.WriteService.ConnectorManagerWriteServices;
constructor(
@Inject('CONNECTOR_PACKAGE') private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.connectorServiceWrite =
this.grpcClient.getService<ConnectorManager.WriteService.ConnectorManagerWriteServices>(
ConnectorManager.ProtoServices.ConnectorManagerWriteServices,
);
this.connectorServiceRead =
this.grpcClient.getService<ConnectorManager.ReadService.ConnectorManagerReadServices>(
ConnectorManager.ProtoServices.ConnectorManagerReadServices,
);
}
async uploadConnector(uploadConnector) {
const connector: ConnectorManager.Entities.ConnectorCreateRequest = {
file: {
buffer: uploadConnector.file.buffer,
mimetypes: uploadConnector.file.mimetype,
},
connector: JSON.stringify(uploadConnector.connector),
};
const serviceBody: ConnectorManager.Messages.RegisterConnectorRequest = {
connector,
};
const observable = from(
this.connectorServiceWrite.RegisterConnector(serviceBody),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
console.log(err.details);
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'Upload');
return result;
}
async uploadFile(uploadFile) {
const body: ConnectorManager.Messages.UploadFileRequest = {
file: {
buffer: uploadFile.file.buffer,
mimetypes: uploadFile.file.mimetype,
},
name: uploadFile.name,
};
const observable = from(this.connectorServiceWrite.UploadFile(body));
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
console.log(err.details);
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'Upload');
return result;
}
async getAllConnectors(body) {
const observable = from(
this.connectorServiceRead.GetAllConnectors({
search: body.search,
filters: JSON.stringify(body.filters),
}),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'getAllConnectors');
return result;
}
async getConnectorDetails(plugin: string) {
const observable = from(
this.connectorServiceRead.GetConnectorDetails({
plugin,
}),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'getAllConnectors');
return result;
}
async getConnector(plugin: string) {
const observable = from(
this.connectorServiceRead.GetConnector({
plugin,
}),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'getAllConnectors');
return result;
}
async deleteConnector(plugin: string, version: string) {
const observable = from(
this.connectorServiceWrite.DeleteConnector({
version,
plugin,
}),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'getAllConnectors');
return result;
}
async updateConnector({ plugin, changes }) {
const observable = from(
this.connectorServiceWrite.UpdateConnector({
plugin,
changes,
}),
);
const result = await new Promise((resolve, reject) => {
observable.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
}).catch((err) => {
throw new HttpException(
err.details,
err.code === 6 ? HttpStatus.CONFLICT : 400,
);
});
console.log('ConnectorClientService', 'getAllConnectors');
return result;
}
}
@@ -1,18 +1,23 @@
import { credentials } from '@grpc/grpc-js';
import { ClientOptions, Transport } from '@nestjs/microservices';
import {
ProtoPackages,
ProtoPaths,
} from '@victorradael/protospack-v2/dist/lib/Duc';
import { DucProtoFilePath, DucPackages } from '@victorradael/protospack';
export class AuthClient {
export class DucClient {
config(): ClientOptions {
return {
transport: Transport.GRPC,
options: {
url: process.env.DUC_URL,
package: DucPackages,
credentials: credentials.createSsl(),
protoPath: DucProtoFilePath,
package: ProtoPackages.WritePackage,
credentials: process.env.LOCAL_ENV
? undefined
: credentials.createSsl(),
protoPath: ProtoPaths.WriteFilePath,
loader: {
keepCase: true,
enums: String,
objects: true,
arrays: true,
+3 -1
View File
@@ -10,7 +10,9 @@ export class InputsClientConfiguration {
options: {
url: process.env.INFACTORY_URL,
package: InputPackages,
credentials: credentials.createSsl(),
credentials: process.env.LOCAL_ENV
? undefined
: credentials.createSsl(),
protoPath: InputProtoFilePath,
loader: {
enums: String,
+153 -62
View File
@@ -1,101 +1,112 @@
import { OnModuleInit, Inject } from '@nestjs/common';
import {
OnModuleInit,
Inject,
HttpException,
HttpStatus,
NotFoundException,
InternalServerErrorException,
} from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import {
DecodeGrpcStruct,
EncodeJsonToGrpcStruct,
InputCreateS3Request,
InputNewCreateRequest,
InputService,
TestConnectionGetColumnsRequest,
TestConnectionRequest,
} from '@victorradael/protospack';
import axios, { AxiosRequestConfig } from 'axios';
import { lastValueFrom } from 'rxjs';
import { InputModel } from 'src/modules/inputs/dtos/input.model';
import {
objectCamelToSnake,
objectSnakeToCamel,
} from 'src/utils/CaseConverter';
import { mustache } from 'src/utils/mustache';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { URLSearchParams } from 'url';
import { IIdRequest, UpdateInputRequest } from './interfaces';
export class InputsClientService implements OnModuleInit {
private inputService: InputService;
constructor(
@Inject('INPUTS_PACKAGE') private readonly grpcClient: ClientGrpc,
@Inject('OAUTH_SECRETS') private readonly oauthSecrets: OauthSecrets,
) {}
onModuleInit() {
async onModuleInit() {
this.inputService =
this.grpcClient.getService<InputService>('InputService');
}
async create(createInputDto: InputNewCreateRequest) {
async newCreate(createInputDto: InputNewCreateRequest) {
console.log('InputClientService', 'Create');
const createInputResponse = await new Promise((resolve, reject) => {
this.inputService
.NewCreate(objectSnakeToCamel(createInputDto))
.subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const input = await lastValueFrom(
this.inputService.NewCreate(objectSnakeToCamel(createInputDto)),
);
objectCamelToSnake(input);
return input;
}
async createGeneric(createInputGeneric) {
console.log('InputClientService', 'Create');
const { info, ...data } = createInputGeneric;
// data.credentials = await this.getInputTokens(data);
if (data.options) delete data.options;
if (data.credentials.oauth_code) delete data.credentials.oauth_code;
const grpcPayload = {
input: EncodeJsonToGrpcStruct(data),
info,
};
objectSnakeToCamel(grpcPayload);
const structReturn = await lastValueFrom(
this.inputService.Create(grpcPayload),
).catch((err: { details: string }) => {
if (err.details === 'Item Not found!') {
throw new NotFoundException('Input not found');
}
throw new InternalServerErrorException(err.details);
});
objectCamelToSnake(structReturn);
const inputCreated = DecodeGrpcStruct(structReturn.input);
return { input: inputCreated };
}
async createS3Inputs(createInputDto: InputCreateS3Request) {
console.log('InputClientService', 'Create');
objectSnakeToCamel(createInputDto);
const createInputResponse = await lastValueFrom(
this.inputService.CreateS3(createInputDto),
).catch((e) => {
throw new HttpException(e.details, 500);
});
console.log('done');
objectCamelToSnake(createInputResponse);
return createInputResponse;
}
async findOne(data: IIdRequest) {
console.log('InputClientService', 'FindOne');
const findOneInputResponse = await new Promise((resolve, reject) => {
this.inputService.FindOne(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const findOneInputResponse = await lastValueFrom(
this.inputService.FindOne(objectSnakeToCamel(data)),
).catch((e) => {
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
});
objectCamelToSnake(findOneInputResponse);
return findOneInputResponse;
}
async findAll(data) {
console.log('InputClientService', 'FindAll');
const findAllInputResponse = await new Promise((resolve, reject) => {
this.inputService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(objectCamelToSnake(x));
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const findAllInputResponse = await lastValueFrom(
this.inputService.FindAll(objectSnakeToCamel(data)),
).catch((e) => {
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
});
findAllInputResponse.inputs.forEach((input) => objectCamelToSnake(input));
return findAllInputResponse;
}
@@ -196,4 +207,84 @@ export class InputsClientService implements OnModuleInit {
return getColumnsResponse;
}
async getAuthSecrets(plugin) {
switch (plugin) {
case 'hubspot':
return {
client_id: this.oauthSecrets.hubspot.client_id || '',
client_secret: this.oauthSecrets.hubspot.client_secret || '',
};
}
return {};
}
async getInputTokens(input: InputModel) {
const { credentials } = input;
let credentialsTokens = {};
switch (credentials.connection_type) {
case 'oauth':
credentialsTokens = await this.getOauthTokens(input);
break;
}
return { ...credentials, ...credentialsTokens };
}
async getOauthTokens(input: InputModel) {
const tokens: Record<string, any> = {};
const { options, credentials } = input;
const { oauth } = options;
const { get_tokens_url_params, get_tokens_set_response } = oauth;
const secrets = await this.getAuthSecrets(input.plugin);
const get_tokens_url_params_string = mustache(get_tokens_url_params, {
...secrets,
...credentials,
});
let get_tokens_url_params_obj: Record<string, any> = {};
try {
get_tokens_url_params_obj = JSON.parse(get_tokens_url_params_string);
} catch (error) {
throw new HttpException('Erro transformando get_tokens_url_params', 400);
}
let params = {};
switch (oauth.content_type) {
case 'application/x-www-form-urlencoded':
params = new URLSearchParams(get_tokens_url_params_obj);
params = params.toString();
break;
default:
break;
}
const axiosRequestConfig: AxiosRequestConfig = {
url: oauth.get_tokens_url,
method: oauth.get_tokens_method || 'POST',
data: params,
headers: { 'content-type': oauth.content_type || 'application/json' },
};
const { data } = await axios(axiosRequestConfig).catch((err) => {
console.log(err.response.data);
throw new HttpException(err.response.data.message, err.response.status);
});
for (const key in get_tokens_set_response) {
const responseKey = get_tokens_set_response[key];
tokens[key] = data[responseKey];
}
const { redirect_uri } = get_tokens_url_params_obj;
tokens.redirect_uri = redirect_uri;
return tokens;
}
async getAvailableEntities(data) {
console.log('InputClientService', 'GetAvailableEntities');
objectSnakeToCamel(data);
const response = await lastValueFrom(
this.inputService.GetAvailableEntities(data),
).catch((err) => {
console.log(err);
if (err.details && err.details.includes('400'))
throw new HttpException('Plugin inválido', HttpStatus.BAD_REQUEST);
throw new HttpException(err.details, HttpStatus.INTERNAL_SERVER_ERROR);
});
objectCamelToSnake(response);
return response;
}
}
+3 -1
View File
@@ -9,7 +9,9 @@ export class OutputsClientConfiguration {
options: {
url: process.env.OTFACTORY_URL,
package: OutputPackages,
credentials: credentials.createSsl(),
credentials: process.env.LOCAL_ENV
? undefined
: credentials.createSsl(),
protoPath: OutputProtoFilePath,
loader: {
enums: String,
+33
View File
@@ -0,0 +1,33 @@
import { Logger, OnModuleInit, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { ProtoServices } from '@victorradael/protospack-v2/dist/lib/Duc';
import { PermissionsProtoService as PermissionsServiceInterface } from '@victorradael/protospack-v2/dist/lib/Duc/interfaces/write-service';
import {
Empty,
InjectPermissionsRequest,
} from '@victorradael/protospack-v2/dist/lib/Duc/interfaces/messages';
import grpcHandler from '../../utils/grpcHandler';
export class PermissionsClientService implements OnModuleInit {
private readonly logger = new Logger(PermissionsClientService.name);
private permissionsService: PermissionsServiceInterface;
constructor(@Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc) {}
onModuleInit() {
this.permissionsService =
this.grpcClient.getService<PermissionsServiceInterface>(
ProtoServices.PermissionsProtoService,
);
}
async injectPermissions({ permissions }: InjectPermissionsRequest) {
this.logger.log('InjectPermissions');
return grpcHandler<Empty>(
this.permissionsService.InjectPermissions({ permissions }),
);
}
}
+3 -1
View File
@@ -12,7 +12,9 @@ export class PipelinesClientConfiguration {
options: {
url: process.env.PIFACTORY_URL,
package: PipelinePackages,
credentials: credentials.createSsl(),
credentials: process.env.LOCAL_ENV
? undefined
: credentials.createSsl(),
protoPath: PipelineProtoFilePath,
loader: {
enums: String,
+18 -23
View File
@@ -1,9 +1,15 @@
import { Inject, OnModuleInit } from '@nestjs/common';
import {
BadRequestException,
Inject,
InternalServerErrorException,
OnModuleInit,
} from '@nestjs/common';
import { ClientGrpc, Payload } from '@nestjs/microservices';
import {
PipelineServicesNames,
PipelinesServiceInterface,
} from '@victorradael/protospack';
import { lastValueFrom } from 'rxjs';
import {
objectCamelToSnake,
objectSnakeToCamel,
@@ -30,27 +36,16 @@ export class PipelinesClientService implements OnModuleInit {
async create(@Payload() createPipelineDto: ICreatePipelineDto) {
console.log('PipelinesClientService', 'Create');
const createPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService
.Create(objectSnakeToCamel(createPipelineDto))
.subscribe({
next(x) {
resolve(x);
},
error(err) {
console.log('Observable Error');
reject(err);
},
complete() {
console.log('done');
},
});
})
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
const createPipelineResponse = await lastValueFrom(
this.pipelineService.Create(objectSnakeToCamel(createPipelineDto)),
).catch((error: { details: string }) => {
if (error.details.includes('INVALID_REQUEST')) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [errorType, message] = error.details.split('|');
throw new BadRequestException(message);
}
throw new InternalServerErrorException(error.details);
});
return createPipelineResponse;
}
@@ -61,7 +56,7 @@ export class PipelinesClientService implements OnModuleInit {
const findAllPipelineResponse = await new Promise((resolve, reject) => {
this.pipelineService.FindAll(objectSnakeToCamel(data)).subscribe({
next(x) {
resolve(x);
resolve(x.pipelines);
},
error(err) {
console.log('Observable Error');
+1 -1
View File
@@ -10,7 +10,7 @@ export class TransformationsClientConfiguration {
return {
transport: Transport.GRPC,
options: {
url: process.env.TRFACTORY_URL,
url: process.env.INFACTORY_URL,
package: TransformationPackages,
credentials: credentials.createSsl(),
protoPath: TransformationProtoFilePath,
+8 -2
View File
@@ -3,6 +3,8 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { writeFileSync } from 'fs';
import helmet from 'helmet';
import documentEmpty from '../swagger_empty.json';
import { AppModule } from './app.module';
async function bootstrap() {
@@ -18,7 +20,7 @@ async function bootstrap() {
app.use(helmet());
const config = new DocumentBuilder()
.setTitle('Maestro Grpc Documentation')
.setTitle('Maestro')
.setDescription('Documentation for Maestro gateway')
.setVersion('1.0')
.addBearerAuth()
@@ -26,7 +28,11 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
writeFileSync('./swagger.json', JSON.stringify(document));
SwaggerModule.setup('api', app, document);
if (process.env.ENV != 'stg' && process.env.ENV != 'prd')
SwaggerModule.setup('api', app, document);
const swaggerDoc = process.env.ENV === 'dev' ? document : documentEmpty;
SwaggerModule.setup('api', app, swaggerDoc);
await app.listen(3333);
}
-127
View File
@@ -1,127 +0,0 @@
import { Request, Response, NextFunction } from 'express';
import axios from 'axios';
import {
ForbiddenException,
InternalServerErrorException,
NestMiddleware,
UnauthorizedException,
UseFilters,
} from '@nestjs/common';
import jwkToPem from 'jwk-to-pem';
import { decode, verify } from 'jsonwebtoken';
import { HttpExceptionFilter } from 'src/error/http-exception.filter';
@UseFilters(new HttpExceptionFilter())
export class LoggerMiddleware implements NestMiddleware {
use = async (request: Request, response: Response, next: NextFunction) => {
const idToken = request.get('Dadosfera-User');
const accessToken = request.get('Authorization');
const privateKey = process.env.JWT_PRIVATE_KEY;
const requiredMethod = request.method.trim();
const requiredRoute = request.route.path.split('/')[1].trim();
verify(idToken, privateKey, (err) => {
if (err) {
throw new UnauthorizedException();
}
});
const jwtDecoded: any = decode(idToken);
const permissions = jwtDecoded.user.permissions;
const clienId = jwtDecoded.user.customerId;
const customer = jwtDecoded.user.customer;
const userId = jwtDecoded.user.id;
await verifyToken(accessToken);
let havePermission = false;
permissions.forEach((permission) => {
permission = permission.split('/');
const method = permission[0].trim();
const route = permission[1].trim();
if (method === requiredMethod && route === requiredRoute) {
havePermission = true;
}
});
if (havePermission) {
request.body.info = {
customer_id: clienId,
user_id: userId,
customer,
};
next();
} else {
throw new ForbiddenException();
}
};
}
let pems: { [key: string]: Record<string, unknown> }[];
const setUp = async (region: string, id: string) => {
const URL = `https://cognito-idp.${region}.amazonaws.com/${id}/.well-known/jwks.json`;
try {
const response = await axios.get(URL);
if (response.status !== 200) {
throw new InternalServerErrorException();
}
const data = await response.data;
const { keys } = data;
pems = keys.map((key: any) => {
const modulus = key.n;
const exponent = key.e;
const keyType = key.kty;
const jwk = { kty: keyType, n: modulus, e: exponent };
const pem = jwkToPem(jwk);
const keyId = key.kid;
return { [keyId]: pem };
});
} catch (error) {
// console.log(error);
// console.log('Error! Unable to download JWKs');
}
};
const verifyToken = async (accessToken: string) => {
const awsRegion = process.env.AWS_REGION;
const awsPoolId = process.env.AWS_IDENTITY_POOL_ID;
try {
await setUp(awsRegion, awsPoolId);
if (!accessToken) {
throw new UnauthorizedException();
}
const user: any = decode(accessToken, { complete: true });
if (user === null) {
throw new UnauthorizedException();
}
const { kid } = user.header;
const pem = pems.filter((item: any) => item[kid]);
const pemValue: any = pem[0][kid];
if (!pem) {
throw new UnauthorizedException();
}
verify(accessToken, pemValue, (err: any) => {
if (err) {
throw new UnauthorizedException();
}
return;
});
} catch (error) {
throw new UnauthorizedException();
}
};
+175 -17
View File
@@ -1,27 +1,185 @@
import { Body, Controller, Post, UnauthorizedException } from '@nestjs/common';
import {
Logger,
Body,
Controller,
Headers,
Post,
HttpCode,
HttpStatus,
OnApplicationBootstrap,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
AuthSignInRequest,
AuthRefreshAccessTokenRequest,
AuthChangePasswordRequest,
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
AuthConfirmResetPasswordRequest,
AuthEnableTotpMfaRequest,
AuthDisableTotpMfaRequest,
AuthVerifyTotpMfaRequest,
} from '@victorradael/protospack-v2/dist/lib/Duc/interfaces/messages';
import { AuthClientService } from 'src/clients/auth/client.service';
import { AuthClientService } from '../../clients/auth/client.service';
import { PermissionsClientService } from '../../clients/permissions/client.service';
import { Permissions } from '../../authentication/permissions.enum';
interface ISignIn {
username: string;
password: string;
}
import ErrorBuilder from '../../utils/ErrorBuilder';
import { LoginDto } from './dtos/login';
@ApiTags('Auth')
@Controller('auth')
export class AuthController {
constructor(private authClient: AuthClientService) {}
export class AuthController implements OnApplicationBootstrap {
private readonly logger = new Logger(AuthController.name);
@Post()
async signIn(@Body() { username, password }: ISignIn) {
console.log(`/auth`, 'SignIn');
constructor(
private authClient: AuthClientService,
private permissionsClient: PermissionsClientService,
) {}
const tokens = await this.authClient
.signIn({ username, password })
.then((result) => result)
.catch((err) => {
throw new UnauthorizedException(err.message);
// internally used to send permissions to duc on microservice startup
async onApplicationBootstrap() {
this.logger.log('sending permissions to DUC...');
const permissions = Object.values(Permissions).flatMap((namespace) =>
Object.values(namespace),
);
return this.permissionsClient
.injectPermissions({ permissions })
.catch((err: ErrorBuilder) => {
if (
err.code === 'No connection established' &&
process.env.LOCAL_ENV === 'true'
) {
return this.logger.log(
"couldn't connect to DUC. suppresing in local env",
);
}
throw err;
});
}
return tokens;
@Post('sign-in')
@HttpCode(HttpStatus.OK)
async signIn(@Body() { username, password, totp }: AuthSignInRequest) {
this.logger.log(`/auth`, 'SignIn');
return this.authClient.signIn({ username, password, totp });
}
@Post('refresh-access-token')
@HttpCode(HttpStatus.OK)
async refreshAccessToken(
@Body() { refreshToken }: AuthRefreshAccessTokenRequest,
) {
this.logger.log(`/auth`, 'RefreshAccessToken');
return this.authClient.refreshAccessToken({ refreshToken });
}
@Post('change-password')
@HttpCode(HttpStatus.OK)
async changePassword(
@Body() body: AuthChangePasswordRequest,
@Headers() headers,
) {
this.logger.log(`/auth`, 'change-password');
const { oldPassword, newPassword } = body;
const { authorization: accessToken } = headers;
return this.authClient.changePassword({
accessToken,
oldPassword,
newPassword,
});
}
@Post('reset-password')
@HttpCode(HttpStatus.OK)
async resetPassword(@Body() body: AuthResetPasswordRequest) {
this.logger.log(`/auth`, 'reset-password');
const { username } = body;
return this.authClient.resetPassword({ username });
}
@Post('verify-reset-password-code')
@HttpCode(HttpStatus.OK)
async verifyResetPasswordCode(
@Body() body: AuthVerifyResetPasswordCodeRequest,
) {
this.logger.log(`/auth`, 'verify-reset-password-code');
const { username, code } = body;
return this.authClient.verifyResetPasswordCode({ username, code });
}
@Post('confirm-reset-password')
@HttpCode(HttpStatus.OK)
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
this.logger.log(`/auth`, 'confirm-reset-password');
const { username, code, newPassword } = body;
return this.authClient.confirmResetPassword({
username,
code,
newPassword,
});
}
@Post('enable-totp')
@HttpCode(HttpStatus.OK)
async enableTotpMFA(
@Body() body: AuthEnableTotpMfaRequest,
@Headers() headers,
) {
this.logger.log(`/auth`, 'enable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.enableTotpMFA({ accessToken, password });
}
@Post('disable-totp')
@HttpCode(HttpStatus.OK)
async disableTotpMFA(
@Body() body: AuthDisableTotpMfaRequest,
@Headers() headers,
) {
this.logger.log(`/auth`, 'disable-totp');
const { password } = body;
const { authorization: accessToken } = headers;
return this.authClient.disableTotpMFA({ accessToken, password });
}
@Post('dismiss-totp')
@HttpCode(HttpStatus.OK)
async dismissTotpMFA(@Headers() headers) {
this.logger.log(`/auth`, 'disable-totp');
const { authorization: accessToken } = headers;
return this.authClient.dismissTotpMFA({ accessToken });
}
@Post('verify-totp')
@HttpCode(HttpStatus.OK)
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
this.logger.log(`/auth`, 'enable-totp');
const { totp } = body;
const { authorization: accessToken } = headers;
return this.authClient.verifyTotp({ accessToken, totp });
}
}
-5
View File
@@ -1,5 +0,0 @@
describe('PipelinesGrpcServerService', () => {
it('should be defined', () => {
expect(2 + 2).toBe(4);
});
});
+12
View File
@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class LoginDto {
@ApiProperty()
username: string;
@ApiProperty()
password: string;
@ApiProperty({ required: false })
totp: string;
}
+60 -91
View File
@@ -7,9 +7,31 @@ import {
Post,
Query,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
import { Permissions } from '../../authentication/permissions.enum';
import { CatalogService } from './catalog.service';
@ApiTags('Catalog')
@Controller('catalog')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
default:
action = req.method;
}
return user.permissions.includes(Permissions.CATALOG[action].seqid);
})
export class CatalogController {
constructor(private catalogService: CatalogService) {}
@@ -17,9 +39,7 @@ export class CatalogController {
async catalogAll(@Body() body) {
console.log(`/catalog`, 'ON CATALOG ALL ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.catalogAll(body);
const res = await this.catalogService.catalogAll(body);
return res;
}
@@ -28,9 +48,7 @@ export class CatalogController {
async dataAppsAll(@Body() body) {
console.log(`/catalog`, 'ON FIND ALL DATA APPS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.dataAppsAll(body);
const res = await this.catalogService.dataAppsAll(body);
return res;
}
@@ -39,9 +57,8 @@ export class CatalogController {
async dataAppsOne(@Param() params, @Body() body) {
console.log(`/catalog`, 'ON FIND ONE DATA APP ROUTE');
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.dataAppsOne(id, body);
const res = await this.catalogService.dataAppsOne(id, body);
return res;
}
@@ -50,9 +67,7 @@ export class CatalogController {
async getAllDashboardMetabase(@Body() body) {
console.log(`/catalog`, 'ON GET ALL DASHBOARDS METABASE ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getAllDashboardMetabase(body);
const res = await this.catalogService.getAllDashboardMetabase(body);
return res;
}
@@ -62,9 +77,7 @@ export class CatalogController {
const { id } = params;
console.log(`/catalog`, 'ON GET ONE DASHBOARD METABASE ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getOneDashboardMetabase(id, body);
const res = await this.catalogService.getOneDashboardMetabase(id, body);
return res;
}
@@ -73,12 +86,11 @@ export class CatalogController {
async getAllTableMetadata(@Body() body, @Query() query) {
console.log(`/catalog`, 'ON GET ALL TABLES METADATA ROUTE');
const catalogService = new CatalogService();
if (!query) {
const res = await catalogService.getAllTableMetadata(body);
const res = await this.catalogService.getAllTableMetadata(body);
return res;
} else {
const res = await catalogService.getOneTableMetadata(body, query);
const res = await this.catalogService.getOneTableMetadata(body, query);
return res;
}
}
@@ -88,9 +100,7 @@ export class CatalogController {
const { id } = params;
console.log(`/catalog`, 'ON DELETE ONE TABLE METADATA ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.deleteOneTableMetadata(id, body);
const res = await this.catalogService.deleteOneTableMetadata(id, body);
return res;
}
@@ -99,9 +109,7 @@ export class CatalogController {
async getOneColumnMetadata(@Body() body, @Query() params) {
console.log(`/catalog`, 'ON GET ONE COLUMN METADATA ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getOneColumnMetadata(body, params);
const res = await this.catalogService.getOneColumnMetadata(body, params);
return res;
}
@@ -111,9 +119,7 @@ export class CatalogController {
console.log(`/catalog`, 'ON DELETE ONE COLUMN METADATA ROUTE');
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteOneColumnMetadata(id, body);
const res = await this.catalogService.deleteOneColumnMetadata(id, body);
return res;
}
@@ -122,9 +128,7 @@ export class CatalogController {
async getOneDataPreview(@Body() body, @Query() params) {
console.log(`/catalog`, 'ON GET ONE DATAPREVIEW ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getOneDataPreview(body, params);
const res = await this.catalogService.getOneDataPreview(body, params);
return res;
}
@@ -133,9 +137,7 @@ export class CatalogController {
async getDataStatus(@Body() body) {
console.log(`/catalog`, 'ON GET DATA STATUS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataStatus(body);
const res = await this.catalogService.getDataStatus(body);
return res;
}
@@ -144,9 +146,7 @@ export class CatalogController {
async createDataStatus(@Body() body) {
console.log(`/catalog`, 'ON CREATE DATA STATUS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createDataStatus(body);
const res = await this.catalogService.createDataStatus(body);
return res;
}
@@ -155,9 +155,7 @@ export class CatalogController {
async getDataDescription(@Body() body) {
console.log(`/catalog`, 'ON GET DATA DESCRIPTION ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataDescription(body);
const res = await this.catalogService.getDataDescription(body);
return res;
}
@@ -166,9 +164,7 @@ export class CatalogController {
async createDataDescription(@Body() body) {
console.log(`/catalog`, 'ON CREATE DATA DESCRIPTION ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createDataDescription(body);
const res = await this.catalogService.createDataDescription(body);
return res;
}
@@ -177,9 +173,7 @@ export class CatalogController {
async getDataDocs(@Body() body) {
console.log(`/catalog`, 'ON GET DATA DOCS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataDocs(body);
const res = await this.catalogService.getDataDocs(body);
return res;
}
@@ -188,9 +182,7 @@ export class CatalogController {
async createDataDocs(@Body() body) {
console.log(`/catalog`, 'ON CREATE DATA DOCS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createDataDocs(body);
const res = await this.catalogService.createDataDocs(body);
return res;
}
@@ -199,9 +191,7 @@ export class CatalogController {
async getDataRating(@Body() body) {
console.log(`/catalog`, 'ON GET DATA RATING ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataRating(body);
const res = await this.catalogService.getDataRating(body);
return res;
}
@@ -210,32 +200,31 @@ export class CatalogController {
async createDataRating(@Body() body) {
console.log(`/catalog`, 'ON GET DATA RATING ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createDataRating(body);
const res = await this.catalogService.createDataRating(body);
return res;
}
@Get('summary-rating/:id')
async getSummaryRating(@Param() params, @Body() body) {
async getSummaryRating() {
console.log(`/catalog`, 'ON GET SUMMARY RATING ROUTE');
const { id } = params;
//const { id } = params;
const catalogService = new CatalogService();
//
const res = await catalogService.getSummaryRating(id, body);
//const res = await this.catalogService.getSummaryRating(id, body);
return res;
return {
avg_rating: 0,
rating_count: 0,
};
}
@Get('data-comment')
async getDataComment(@Body() body) {
console.log(`/catalog`, 'ON GET DATA COMMENT ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataComment(body);
const res = await this.catalogService.getDataComment(body);
return res;
}
@@ -244,9 +233,7 @@ export class CatalogController {
async createDataComment(@Body() body) {
console.log(`/catalog`, 'ON GET DATA COMMENT ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createDataComment(body);
const res = await this.catalogService.createDataComment(body);
return res;
}
@@ -255,9 +242,7 @@ export class CatalogController {
async getDataReview(@Body() body, @Query() params) {
console.log(`/catalog`, 'ON GET DATA REVIEW ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getDataReview(body, params);
const res = await this.catalogService.getDataReview(body, params);
return res;
}
@@ -266,9 +251,7 @@ export class CatalogController {
async createTags(@Body() body) {
console.log(`/catalog`, 'ON CREATE TAG ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createTags(body);
const res = await this.catalogService.createTags(body);
return res;
}
@@ -277,9 +260,7 @@ export class CatalogController {
async findAllTags(@Body() body) {
console.log(`/catalog`, 'ON FIND ALL TAGS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.findAllTags(body);
const res = await this.catalogService.findAllTags(body);
return res;
}
@@ -289,9 +270,7 @@ export class CatalogController {
console.log(`/catalog`, 'ON DELETE TAG ROUTE');
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTags(id, body);
const res = await this.catalogService.deleteTags(id, body);
return res;
}
@@ -300,9 +279,7 @@ export class CatalogController {
async createTableTags(@Body() body) {
console.log(`/catalog`, 'ON CREATE TABLE TAG ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.createTableTags(body);
const res = await this.catalogService.createTableTags(body);
return res;
}
@@ -311,9 +288,7 @@ export class CatalogController {
async getAllTableTags(@Body() body) {
console.log(`/catalog`, 'ON GET ALL TABLE TAGS ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.findAllTableTags(body);
const res = await this.catalogService.findAllTableTags(body);
return res;
}
@@ -323,9 +298,7 @@ export class CatalogController {
console.log(`/catalog`, 'ON CREATE TABLE TAGS ROUTE');
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTableTags(id, body);
const res = await this.catalogService.deleteTableTags(id, body);
return res;
}
@@ -334,9 +307,7 @@ export class CatalogController {
async getTableRules(@Body() body) {
console.log(`/catalog`, 'ON GET TABLE RULES ROUTE');
const catalogService = new CatalogService();
const res = await catalogService.getTableRules(body);
const res = await this.catalogService.getTableRules(body);
return res;
}
@@ -346,9 +317,7 @@ export class CatalogController {
console.log(`/catalog`, 'ON DELETE TABLE RULES ROUTE');
const { id } = params;
const catalogService = new CatalogService();
const res = await catalogService.deleteTableRules(id, body);
const res = await this.catalogService.deleteTableRules(id, body);
return res;
}
+46 -315
View File
@@ -1,48 +1,31 @@
import axios from 'axios';
class CatalogService {
_getNimbusUrl(body) {
const customer = body.info.customer.toLowerCase();
if (process.env.ENV === 'prd') {
return `https://nimbus-${customer}.dadosfera.ai`;
}
return `https://nimbus-${customer}.${process.env.ENV}.dadosfera.ai`;
}
async catalogAll(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/all/`);
return data;
}
async dataAppsAll(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data_apps/`);
return data;
}
async dataAppsOne(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/data_apps/${id}/`,
);
@@ -51,16 +34,7 @@ class CatalogService {
}
async getAllDashboardMetabase(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/dashboard-metabase/`,
);
@@ -68,16 +42,7 @@ class CatalogService {
}
async getOneDashboardMetabase(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/dashboard-metabase/${id}`,
);
@@ -91,16 +56,7 @@ class CatalogService {
}
async getAllTableMetadata(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/table-metadata/`,
);
@@ -108,16 +64,7 @@ class CatalogService {
}
async getOneTableMetadata(body, params) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/table-metadata/`,
{ params: params },
@@ -126,33 +73,15 @@ class CatalogService {
}
async deleteOneTableMetadata(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const { data } = await axios.get(
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${nimbusUrl}/api/catalog/table-metadata/${id}`,
);
return data;
}
async getOneColumnMetadata(body, params) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/column-metadata/`,
{ params: params },
@@ -161,33 +90,15 @@ class CatalogService {
}
async deleteOneColumnMetadata(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const { data } = await axios.get(
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${nimbusUrl}/api/catalog/column-metadata/${id}/`,
);
return data;
}
async getOneDataPreview(body, params) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-preview/`, {
params: params,
});
@@ -195,31 +106,13 @@ class CatalogService {
}
async getDataStatus(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-status/`);
return data;
}
async createDataStatus(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-status/`,
body,
@@ -228,16 +121,7 @@ class CatalogService {
}
async getDataDescription(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/data-description/`,
);
@@ -245,16 +129,7 @@ class CatalogService {
}
async createDataDescription(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-description/`,
body,
@@ -263,31 +138,13 @@ class CatalogService {
}
async getDataDocs(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-docs/`);
return data;
}
async createDataDocs(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-docs/`,
body,
@@ -296,31 +153,13 @@ class CatalogService {
}
async getDataRating(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-rating/`);
return data;
}
async createDataRating(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-rating/`,
body,
@@ -329,16 +168,7 @@ class CatalogService {
}
async getSummaryRating(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/summary-rating/${id}`,
);
@@ -346,31 +176,13 @@ class CatalogService {
}
async getDataComment(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-comment/`);
return data;
}
async createDataComment(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/data-comment/`,
body,
@@ -379,16 +191,7 @@ class CatalogService {
}
async getDataReview(body, params) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-review/`, {
params: params,
});
@@ -396,46 +199,19 @@ class CatalogService {
}
async createTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(`${nimbusUrl}/api/catalog/tags/`, body);
return data;
}
async findAllTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(`${nimbusUrl}/api/catalog/tags/`, body);
return data;
}
async findAllTableTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/table-tags/`,
body,
@@ -444,31 +220,13 @@ class CatalogService {
}
async deleteTags(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const { data } = await axios.get(`${nimbusUrl}/api/catalog/tags/${id}`);
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(`${nimbusUrl}/api/catalog/tags/${id}`);
return data;
}
async createTableTags(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.post(
`${nimbusUrl}/api/catalog/table-tags/`,
body,
@@ -477,33 +235,15 @@ class CatalogService {
}
async deleteTableTags(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const { data } = await axios.get(
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${nimbusUrl}/api/catalog/table-tags/${id}`,
);
return data;
}
async getTableRules(body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.get(
`${nimbusUrl}/api/catalog/table-rules/`,
body,
@@ -512,17 +252,8 @@ class CatalogService {
}
async deleteTableRules(id, body) {
const customer =
body.info.customer.toLowerCase() === 'dadosfera'
? ``
: `-${body.info.customer.toLowerCase()}`;
const nimbusUrl =
process.env.ENV === 'prd'
? `https://nimbus-${body.info.customer.toLowerCase()}.dadosfera.ai`
: `${process.env.NIMBUS_BASE_URL}${customer}.${process.env.ENV}.dadosfera`;
const { data } = await axios.get(
const nimbusUrl = this._getNimbusUrl(body);
const { data } = await axios.delete(
`${nimbusUrl}/api/catalog/table-rules/${id}`,
);
return data;
@@ -0,0 +1,210 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Put,
Post,
Query,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { ConnectorClientService } from 'src/clients/connector/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';
import { UploadFileDto } from './dtos/upload-file';
@ApiTags('connectors')
@ApiBearerAuth()
@Controller('connectors')
export class ConnectorController {
constructor(private connectorClientService: ConnectorClientService) {}
@Post()
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async uploadConnector(
@UploadedFile() file,
@Body() body: CreateConnectorDto,
) {
console.log(`/upload`, 'Upload Connector Route');
const response = await this.connectorClientService.uploadConnector({
file,
connector: JSON.parse(body.connector),
});
return response;
}
@Post('/upload')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file, @Body() { name }: UploadFileDto) {
console.log(`/upload`, 'Upload Connector Route');
const response = await this.connectorClientService.uploadFile({
file,
name,
});
return response;
}
@Get()
async getAllConnectors(@Query() queries: GetAllDto) {
console.log(`/upload`, 'Upload Connector Route');
const { filters, search } = queries;
const response: any = await this.connectorClientService.getAllConnectors({
filters: filters || {},
search: search || '',
});
const connectors = JSON.parse(response.connectors).connectors;
return {
message: response.message,
connectors,
};
}
@Get('/:plugin')
async getConnector(
@Param('plugin') plugin: string,
@Query('version') version: string,
) {
console.log(`/upload`, 'Upload Connector Route');
const pluginId = `${plugin}-${version}`;
const response: any = await this.connectorClientService.getConnector(
pluginId,
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Get('/:plugin/details')
async getConnectorDetails(
@Param('plugin') plugin: string,
@Query('version') version: string,
) {
console.log(`/upload`, 'Upload Connector Route');
const pluginId = `${plugin}-${version}`;
const response: any = await this.connectorClientService.getConnectorDetails(
pluginId,
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin')
@ApiConsumes('multipart/form-data')
async updateConnector(
@Param('plugin') plugin: string,
@Body() body: UpdateDto,
) {
console.log(`/upload`, 'Upload Connector Route');
const changes = body;
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin/add-tag')
async addTagOnConnector(
@Param('plugin') plugin: string,
@Body() body: AddTagDto,
) {
console.log(`/upload`, 'Upload Connector Route');
const { tags } = body;
if (!tags || !(typeof tags === 'object') || !tags.length) {
throw new HttpException('Not found tags attributes', 400);
}
const changes = { plugin, tags };
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Put('/:plugin/remove-tag')
async removeTagOnConnector(
@Param('plugin') plugin: string,
@Body() body: RemoveTagDto,
) {
console.log(`/upload`, 'Upload Connector Route');
const { tags } = body;
const remove = tags;
if (!remove || !(typeof remove === 'object') || !remove.length) {
throw new HttpException('Not found remove attribute', 400);
}
const changes = { plugin, remove };
const response: any = await this.connectorClientService.updateConnector({
plugin,
changes: JSON.stringify(changes),
});
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
@Delete('/:plugin')
async deleteConnector(
@Param('plugin') plugin: string,
@Body() { version }: DeleteConnectorDto,
) {
console.log(`/upload`, 'Upload Connector Route');
const response: any = await this.connectorClientService.deleteConnector(
plugin,
version,
);
return {
message: response.message || 'ok',
connector: { ...JSON.parse(response.connector) },
};
}
}
+6
View File
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class AddTagDto {
@ApiProperty()
tags: string[];
}
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreateConnectorDto {
@ApiProperty({ format: 'binary' })
file: string;
@ApiProperty()
connector: string;
}
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class DeleteConnectorDto {
@ApiProperty()
version: string;
}
+9
View File
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class GetAllDto {
@ApiProperty()
search: string;
@ApiProperty()
filters: object;
}
+6
View File
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
export class RemoveTagDto {
@ApiProperty()
tags: string[];
}
+42
View File
@@ -0,0 +1,42 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateDto {
@ApiProperty({ format: 'binary', required: false })
file?: string;
@ApiProperty()
version: string;
@ApiProperty({ required: false })
category?: string;
@ApiProperty({ required: false })
plugin?: string;
@ApiProperty({ required: false })
image?: string;
@ApiProperty({ required: false })
docs?: string;
@ApiProperty({ required: false })
name?: string;
@ApiProperty({
required: false,
description: 'skip_select_entities: boolean skip_select_columns: boolean;',
})
options?: {
skip_select_entities: boolean;
skip_select_columns: boolean;
};
@ApiProperty({
required: false,
type: 'array',
items: {
properties: {},
},
})
connection_controls?: object[];
}
@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class UploadFileDto {
@ApiProperty({ format: 'binary' })
file: string;
@ApiProperty()
name: string;
}
+2
View File
@@ -1,6 +1,8 @@
import { Controller, Get } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { HealthService } from './health.service';
@ApiTags('Health')
@Controller('health')
export class HealthController {
constructor(private readonly healthService: HealthService) {}
+206
View File
@@ -0,0 +1,206 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Method } from 'axios';
export class InputModel {
category: string;
plugin: string;
name: string;
cron?: string;
credentials?: {
connection_type: string;
oauth: string;
oauth_code: string;
start_date: string;
};
options?: {
oauth?: {
get_tokens_url: string;
get_tokens_method?: Method;
get_tokens_url_params: string;
get_tokens_set_response: Record<string, string>;
content_type: string;
};
};
}
class GoogleAnalyticsClientSecrets {
@ApiProperty()
type: string;
@ApiProperty()
project_id: string;
@ApiProperty()
private_key_id: string;
@ApiProperty()
private_key: string;
@ApiProperty()
client_email: string;
@ApiProperty()
client_id: string;
@ApiProperty()
auth_uri: string;
@ApiProperty()
token_uri: string;
@ApiProperty()
auth_provider_x509_cert_url: string;
@ApiProperty()
client_x509_cert_url: string;
}
class OauthObject {
@ApiPropertyOptional()
get_tokens_url: string;
@ApiPropertyOptional()
get_tokens_url_params: string;
@ApiPropertyOptional()
get_tokens_set_response: Record<string, any>;
@ApiPropertyOptional()
content_type: string;
}
class InputOptions {
@ApiPropertyOptional()
oauth: OauthObject;
@ApiPropertyOptional()
skip_select_columns: true;
@ApiPropertyOptional()
skip_select_entities: false;
@ApiPropertyOptional()
skip_transformation: true;
}
class CredentialsJdbc {
@ApiPropertyOptional()
jdbc_user: string;
@ApiPropertyOptional()
jdbc_password: string;
@ApiPropertyOptional()
database: string;
@ApiPropertyOptional()
endpoint: string;
@ApiPropertyOptional()
port: string;
@ApiPropertyOptional()
engine: string;
@ApiPropertyOptional()
schema: string;
}
class FileFormatParams {
@ApiPropertyOptional()
file_format: string;
@ApiPropertyOptional()
encoding: string;
}
class Credentials extends CredentialsJdbc {
@ApiProperty()
connection_type: string;
@ApiPropertyOptional()
client_aws_access_key_id: string;
@ApiPropertyOptional()
client_aws_secret_access_key: string;
@ApiPropertyOptional()
client_bucket: string;
@ApiPropertyOptional()
file_to_extract: string;
@ApiPropertyOptional()
file_format_params: FileFormatParams;
@ApiPropertyOptional()
view_id: string;
@ApiPropertyOptional()
client_secrets: GoogleAnalyticsClientSecrets;
@ApiPropertyOptional()
start_date: string;
@ApiPropertyOptional()
end_date: string;
@ApiPropertyOptional()
oauth_code: string;
}
class Column {
@ApiProperty()
name: string;
@ApiProperty()
type: string;
}
class TableColumns {
@ApiProperty()
name: string;
@ApiProperty()
columns: Column[];
@ApiProperty()
references: Column[];
}
export class GetAvailableEntitiesReq {
@ApiProperty()
plugin: string;
}
export class GetAvailableEntitiesRes {
@ApiProperty()
entities: string[];
}
export class TestConnectionGetColumnsReq {
@ApiProperty()
plugin: string;
@ApiProperty()
tables: string[];
@ApiPropertyOptional()
credentials: CredentialsJdbc;
@ApiPropertyOptional()
id: string;
}
export class TestConnectionGetColumnsRes {
@ApiProperty()
tables: TableColumns[];
}
export class TestConnectionReq {
@ApiProperty()
plugin: string;
@ApiProperty()
credentials: CredentialsJdbc;
}
export class TestConnectionRes {
@ApiProperty()
connection_state: boolean;
@ApiProperty()
total_entities: number;
@ApiProperty()
database_tables: string[];
}
export class CreateInputReq {
@ApiPropertyOptional()
id: string;
@ApiProperty()
plugin: string;
@ApiProperty()
category: string;
@ApiProperty()
credentials: Credentials;
@ApiProperty()
name: string;
@ApiPropertyOptional()
options: InputOptions;
}
export class Input {
@ApiProperty()
id: string;
@ApiProperty()
category: string;
@ApiProperty()
plugin: string;
@ApiProperty()
name: string;
@ApiProperty()
cron: string;
@ApiProperty()
credentials: Credentials;
@ApiProperty()
client_id: string;
@ApiProperty()
created_at: string;
@ApiProperty()
updated_at: string;
}
+84 -23
View File
@@ -4,47 +4,114 @@ import {
Delete,
Get,
Param,
Patch,
Post,
Put,
} from '@nestjs/common';
import { InputsService } from './inputs.service';
import { InputsClientService } from 'src/clients/inputs/client.service';
import { UpdateInputRequest } from 'src/clients/inputs/interfaces';
import { InputNewCreateRequest } from '@victorradael/protospack';
import { Permissions } from '../../authentication/permissions.enum';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import {
CreateInputReq,
GetAvailableEntitiesReq,
GetAvailableEntitiesRes,
Input,
TestConnectionGetColumnsReq,
TestConnectionGetColumnsRes,
TestConnectionReq,
TestConnectionRes,
} from './dtos/input.model';
@ApiTags('inputs')
@Controller('inputs')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
case 'PATCH':
action = 'UPDATE_PARTIAL';
break;
default:
action = req.method;
}
if (
`${req.method} ${req.route.path}` ===
'GET /inputs/available-entities/:plugin'
) {
action = 'GET_ENTITIES';
}
return user.permissions.includes(Permissions.INPUT[action].seqid);
})
export class InputsController {
constructor(private inputsClientService: InputsClientService) {}
inputService: InputsService;
constructor(private inputsClientService: InputsClientService) {
this.inputService = new InputsService(this.inputsClientService);
}
@Get('available-entities/:plugin')
@ApiOkResponse({ type: GetAvailableEntitiesRes })
async getAvailableEntities(
@Body() body,
@Param() params: GetAvailableEntitiesReq,
) {
const { info } = body;
const { plugin } = params;
return await this.inputService.getAvailableEntities({ info, plugin });
}
@Post('/test-connection')
async testConnection(@Body() data) {
@ApiOkResponse({ type: TestConnectionRes })
async testConnection(@Body() data: TestConnectionReq) {
console.log(`/test-connection`, 'ON TEST CONNECTION ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.testConnection(data);
const response = await this.inputService.testConnection(data);
return response;
}
@Post('/test-connection/get-columns')
async getColumns(@Body() data) {
@ApiOkResponse({ type: TestConnectionGetColumnsRes })
async getColumns(@Body() data: TestConnectionGetColumnsReq) {
console.log(
`/test-connection/get-columns`,
'ON TEST CONNECTION GET COLUMNS ROUTE',
);
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.getColumns(data);
const response = await this.inputService.getColumns(data);
return response;
}
@Post()
async create(@Body() createInputDto: InputNewCreateRequest) {
@ApiOkResponse({ type: Input })
async create(@Body() createInputDto: CreateInputReq) {
console.log(`/input`, 'ON CREATE ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.create(createInputDto);
const response = await this.inputService.create(createInputDto);
return response;
}
@Post(':id')
@ApiOkResponse({ type: Input })
async reCreate(@Param('id') id: string, @Body() input: CreateInputReq) {
if (!input.id) input.id = id;
console.log(`POST /${input.id}`, 'ON RECREATE ROUTE');
const response = await this.inputService.reCreate(input);
return response;
}
@@ -53,9 +120,7 @@ export class InputsController {
async findAll(@Body() body) {
console.log(`/input`, 'ON FIND ALL ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.findAll(body);
const response = await this.inputService.findAll(body);
return response;
}
@@ -64,23 +129,21 @@ export class InputsController {
async findOne(@Body() body, @Param() params) {
const { id } = params;
console.log(`/input/${id}`, 'ON FIND ONE ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.findOne({ id, ...body });
const response = await this.inputService.findOne({ id, ...body });
return response;
}
@Put(':id')
@Patch(':id')
async update(@Body() updateInputDto: UpdateInputRequest, @Param() params) {
const { id } = params;
const { info } = updateInputDto;
delete updateInputDto.info;
console.log(`/input/${id}`, 'ON UPDATE ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.update(id, updateInputDto, info);
const response = await this.inputService.update(id, updateInputDto, info);
return response;
}
@@ -91,9 +154,7 @@ export class InputsController {
console.log(`/input/${id}`, 'ON DELETE ROUTE');
const inputService = new InputsService(this.inputsClientService);
const response = await inputService.remove({ id, ...data });
const response = await this.inputService.remove({ id, ...data });
return response;
}
+121 -12
View File
@@ -1,26 +1,98 @@
import { Body, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InputNewCreateRequest } from '@victorradael/protospack';
import { Timeout } from '@nestjs/schedule';
import { DecodeGrpcStruct } from '@victorradael/protospack';
import CronParser, { CronExpression } from 'cron-parser';
import { InputsClientService } from 'src/clients/inputs/client.service';
import { IIdRequest, Info } from 'src/clients/inputs/interfaces';
@Injectable()
export class InputsService {
constructor(private inputClient: InputsClientService) {}
secondsInADay = 60 * 60 * 24;
secondsInAnHour = 60 * 60;
async create(@Body() data: InputNewCreateRequest) {
adjustInputPayload(payload) {
if (payload?.input_generic) return DecodeGrpcStruct(payload.input_generic);
if (!payload.input_s3 && !payload.input_jdbc) return payload;
return payload?.input_s3 || payload?.input_jdbc;
}
getDifferenceInSeconds(date1: Date, date2: Date) {
const diffInMs = Math.abs(date2.getTime() - date1.getTime());
return diffInMs / 1000;
}
validateCron(data) {
const { info, cron } = data;
const { customer_tier } = info;
if (!cron) return;
let interval: CronExpression;
try {
const createInputResponse = await this.inputClient.create(data);
return createInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
interval = CronParser.parseExpression(cron);
} catch (error) {
throw new HttpException(
'Intervalo de tempo inválido',
HttpStatus.BAD_REQUEST,
);
}
const nextDate = interval.next().toDate();
const afterNextDate = interval.next().toDate();
const secondsApart = this.getDifferenceInSeconds(nextDate, afterNextDate);
if (customer_tier === 'BASIC' && secondsApart < this.secondsInADay) {
throw new HttpException(
'Intervalo de tempo não pode ser inferior a um dia.',
HttpStatus.FORBIDDEN,
);
} else if (secondsApart < this.secondsInAnHour) {
throw new HttpException(
'Intervalo de tempo não pode ser inferior a uma hora.',
HttpStatus.FORBIDDEN,
);
}
}
async create(@Body() data) {
this.validateCron(data);
let response;
switch (data.plugin.toLowerCase()) {
case 'csv':
case 'json':
case 'parquet':
const { info, ...input } = data;
const inputPayload = this.generateInputS3Payload(input);
response = await this.inputClient.createS3Inputs({
input: inputPayload,
info,
});
break;
case 'oracle':
case 'mysql':
case 'postgresql':
case 'sqlserver':
response = await this.inputClient.newCreate(data);
break;
default:
response = await this.inputClient.createGeneric(data);
break;
}
const adjustedInput = this.adjustInputPayload(response.input);
return { ...response, input: adjustedInput };
}
async reCreate(data) {
const { info, cron } = data;
if (cron) this.validateCron({ info, cron });
const response = await this.inputClient.createGeneric(data);
const adjustedInput = this.adjustInputPayload(response.input);
return { ...response, input: adjustedInput };
}
async getAvailableEntities(data): Promise<{ entities: string[] }> {
return await this.inputClient.getAvailableEntities(data);
}
async findAll(body) {
try {
const findAllInputResponse = await this.inputClient.findAll(body);
const findAllInputResponse: any = await this.inputClient.findAll(body);
if (findAllInputResponse?.inputs?.length) {
findAllInputResponse.inputs = findAllInputResponse.inputs.map((input) =>
this.adjustInputPayload(input),
);
}
return findAllInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
@@ -29,7 +101,12 @@ export class InputsService {
async findOne(idRequest: IIdRequest) {
try {
const findOneInputResponse = await this.inputClient.findOne(idRequest);
const findOneInputResponse: any = await this.inputClient.findOne(
idRequest,
);
findOneInputResponse.input = this.adjustInputPayload(
findOneInputResponse.input,
);
return findOneInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
@@ -37,13 +114,17 @@ export class InputsService {
}
async update(id: string, data, info: Info) {
this.validateCron({ ...data, info });
try {
const updateInputResponse = await this.inputClient.update({
const updateInputResponse: any = await this.inputClient.update({
id,
info,
...data,
});
updateInputResponse.input = this.adjustInputPayload(
updateInputResponse?.input,
);
return updateInputResponse;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
@@ -60,6 +141,7 @@ export class InputsService {
}
}
@Timeout(60000 * 10) // Timeout set for 10 minutes
async testConnection(data) {
try {
const testConnectionInputResponse = await this.inputClient.testConnection(
@@ -82,4 +164,31 @@ export class InputsService {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
}
generateInputS3Payload(payload) {
const { credentials, plugin, cron } = payload;
if (!credentials) return payload;
const {
client_aws_access_key_id,
client_aws_secret_access_key,
file_format_params,
format_file_params,
client_bucket,
file_to_extract,
} = credentials;
const formatedPayload = {
plugin,
cron,
source_bucket: client_bucket,
source_prefix: file_to_extract,
auth_parameters: {
aws_access_key_id: client_aws_access_key_id,
aws_secret_access_key: client_aws_secret_access_key,
},
file_format_params: file_format_params || format_file_params,
};
return formatedPayload;
}
}
+117
View File
@@ -0,0 +1,117 @@
import { Controller, Get, Redirect, Req, UseGuards } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { InputsClientService } from 'src/clients/inputs/client.service';
import { InputsService } from '../inputs/inputs.service';
import { Request } from 'express';
@ApiTags('oauth')
@Controller('oauth')
export class OauthController {
inputService: InputsService;
frontendRedirectUri = '';
constructor(private inputsClientService: InputsClientService) {
this.inputService = new InputsService(this.inputsClientService);
switch (process.env.ENV) {
case 'dev':
case 'stg':
this.frontendRedirectUri = `https://app.${process.env.ENV}.dadosfera.ai/coletix/auth-callback`;
break;
case 'prd':
this.frontendRedirectUri = `https://app.dadosfera.ai/coletix/auth-callback`;
break;
default:
this.frontendRedirectUri = `http://localhost:4200/coletix/auth-callback`;
}
}
@Get('hubspot')
@UseGuards(AuthGuard('hubspot'))
async oauthHubspot() {
return true;
}
@Get('hubspot/callback')
@UseGuards(AuthGuard('hubspot'))
@Redirect()
async oauthHubspotCallback(@Req() req) {
return await this.callback(req, 'hubspot');
}
@Get('googleads')
@UseGuards(AuthGuard('google'))
async oauthGoogleAds() {
return true;
}
@Get('googleads/callback')
@UseGuards(AuthGuard('google'))
@Redirect()
async oauthGoogleAdsCallback(@Req() req) {
return await this.callback(req, 'googleads');
}
@Get('google-sheets')
@UseGuards(AuthGuard('google'))
async oauthGoogleSheets() {
return true;
}
@Get('google-sheets/callback')
@UseGuards(AuthGuard('google'))
@Redirect()
async oauthGoogleSheetsCallback(@Req() req) {
return await this.callback(req, 'google-sheets');
}
@Get('facebook')
@UseGuards(AuthGuard('facebook'))
async oauthFacebook() {
return true;
}
@Get('facebook/callback')
@UseGuards(AuthGuard('facebook'))
@Redirect()
async oauthFacebookCallback(@Req() req) {
return await this.callback(req, 'facebook');
}
@Get('mailchimp')
@UseGuards(AuthGuard('mailchimp'))
async oauthMailchimp() {
return true;
}
@Get('mailchimp/callback')
@UseGuards(AuthGuard('mailchimp'))
@Redirect()
async oauthMailchimpCallback(@Req() req) {
return await this.callback(req, 'mailchimp');
}
async callback(@Req() req: Request, plugin) {
const { error, state: customer_id } = req.query;
if (error)
return {
url: `${this.frontendRedirectUri}?error=${error}`,
};
const request: any = req;
const { authInfo } = request;
if (!authInfo)
return {
url: `${this.frontendRedirectUri}?error=invalid_credentials`,
};
const { accessToken: access_token, refreshToken: refresh_token } = authInfo;
const response = await this.inputService.create({
info: { customer_id, customer: 'customer', user_id: 'empty' },
plugin,
credentials: { access_token, refresh_token },
});
return { url: `${this.frontendRedirectUri}?input_id=${response.input.id}` };
}
}
@@ -0,0 +1,32 @@
import {
Profile,
Strategy,
StrategyOptionWithRequest,
VerifyFunction,
} from 'passport-facebook';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
@Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
const options: StrategyOptionWithRequest = {
clientID: oauthSecrets.facebook.client_id,
clientSecret: oauthSecrets.facebook.client_secret,
callbackURL: oauthSecrets.facebook.redirect_uri,
passReqToCallback: true,
};
const verify: VerifyFunction = (
accessToken: string,
refreshToken: string,
profile: Profile,
done: (error: any, user?: any, info?: any) => void,
) => done(null, profile, { accessToken, refreshToken });
super(options, verify);
}
}
@@ -0,0 +1,68 @@
import { Profile, Strategy, StrategyOptions } from 'passport-google-oauth20';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
redirect_uri: string;
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
const options: StrategyOptions = {
clientID: oauthSecrets.google.client_id,
clientSecret: oauthSecrets.google.client_secret,
callbackURL: '/oauth/googleads/callback',
};
const verify = (
accessToken: string,
refreshToken: string,
profile: Profile,
done,
) => {
console.log('verify');
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
switch (process.env.ENV) {
case 'dev':
case 'stg':
this.redirect_uri = `https://maestro.${process.env.ENV}.dadosfera.ai/oauth`;
break;
case 'prd':
this.redirect_uri = `https://maestro.dadosfera.ai/oauth`;
break;
default:
this.redirect_uri = `/oauth`;
break;
}
}
authenticate(req, options) {
const plugin: string = req.route.path
.replace('/oauth/', '')
.replace('/callback', '');
const { customer_id } = req.query;
options.scope = ['https://www.googleapis.com/auth/userinfo.email'];
options.callbackURL = `${this.redirect_uri}/${plugin}/callback`;
switch (plugin) {
case 'googleads':
options.scope.push('https://www.googleapis.com/auth/adwords');
break;
case 'google-sheets':
options.scope.push(
'https://www.googleapis.com/auth/drive.metadata.readonly',
'https://www.googleapis.com/auth/spreadsheets.readonly',
);
break;
}
options.state = customer_id;
options.accessType = 'offline';
options.prompt = 'consent';
super.authenticate(req, options);
}
}
@@ -0,0 +1,33 @@
import { Strategy } from 'passport-hubspot-oauth2';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
@Injectable()
export class HubspotStrategy extends PassportStrategy(Strategy) {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
super(
{
clientID: oauthSecrets.hubspot.client_id,
clientSecret: oauthSecrets.hubspot.client_secret,
callbackURL: oauthSecrets.hubspot.redirect_uri,
redirectUri: oauthSecrets.hubspot.redirect_uri,
scope:
'automation business-intelligence oauth forms integration-sync sales-email-read crm.lists.read crm.objects.contacts.read crm.schemas.contacts.read crm.objects.companies.read crm.objects.deals.read crm.schemas.companies.read crm.schemas.deals.read crm.objects.owners.read crm.objects.quotes.read crm.schemas.quotes.read crm.objects.line_items.read crm.schemas.line_items.read',
passReqToCallback: true,
},
(accessToken, refreshToken, tokenInfo, profile, done) => {
return done(null, profile, { accessToken, refreshToken, tokenInfo });
},
);
}
authenticate(req, options) {
const { customer_id } = req.query;
options.state = customer_id;
super.authenticate(req, options);
}
}
@@ -0,0 +1,35 @@
import { Strategy } from 'passport-mailchimp';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable, Req } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
@Injectable()
export class MailChimpStrategy extends PassportStrategy(Strategy, 'mailchimp') {
constructor(
@Inject('OAUTH_SECRETS')
private readonly oauthSecrets: OauthSecrets,
) {
const options = {
clientID: oauthSecrets.mailchimp.client_id,
clientSecret: oauthSecrets.mailchimp.client_secret,
callbackURL: oauthSecrets.mailchimp.redirect_uri,
scope: '',
};
const verify = (
accessToken: string,
refreshToken: string,
profile: any,
done,
) => {
return done(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
authenticate(@Req() req, options) {
const { customer_id } = req.query;
options.state = customer_id;
super.authenticate(req, options);
}
}
+23 -1
View File
@@ -8,12 +8,34 @@ import {
Put,
} from '@nestjs/common';
import { Payload } from '@nestjs/microservices';
import { ApiTags } from '@nestjs/swagger';
import { OutputsClientService } from 'src/clients/outputs/client.service';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import { Permissions } from '../../authentication/permissions.enum';
import { OutputsService } from './outputs.service';
@ApiTags('Outputs')
@Controller('outputs')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
default:
action = req.method;
}
return user.permissions.includes(Permissions.OUTPUT[action].seqid);
})
export class OutputsController {
constructor(private outputsClientService: OutputsClientService) {}
constructor(private outputsClientService: OutputsClientService) { }
@Post()
async create(@Body() createOutputDto) {
@@ -7,10 +7,32 @@ import {
Post,
Put,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { PipelinesClientService } from 'src/clients/pipelines/client.service';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import { Permissions } from '../../authentication/permissions.enum';
import { PipelinesService } from './pipelines.service';
@ApiTags('Pipelines')
@Controller('pipelines')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
default:
action = req.method;
}
return user.permissions.includes(Permissions.PIPELINE[action].seqid);
})
export class PipelinesController {
constructor(private pipelinesClientService: PipelinesClientService) {}
+31 -13
View File
@@ -1,4 +1,5 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DecodeGrpcStruct } from '@victorradael/protospack';
import { PipelinesClientService } from 'src/clients/pipelines/client.service';
import { IIdRequest, Info } from 'src/clients/pipelines/interfaces';
import { objectCamelToSnake } from 'src/utils/CaseConverter';
@@ -7,23 +8,33 @@ import { objectCamelToSnake } from 'src/utils/CaseConverter';
export class PipelinesService {
constructor(private pipelineClient: PipelinesClientService) {}
async create(createPipelineDto) {
try {
const createPipelineResponse = await this.pipelineClient.create(
createPipelineDto,
);
return objectCamelToSnake(createPipelineResponse);
} catch (err) {
console.log(err);
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
adjustPayload(payload) {
if (payload.input?.input_generic) {
payload.input = DecodeGrpcStruct(payload.input?.input_generic);
} else {
payload.input = payload.input?.input_s3 || payload.input?.input_jdbc;
}
}
async create(createPipelineDto) {
const createPipelineResponse = await this.pipelineClient.create(
createPipelineDto,
);
const pipeline = objectCamelToSnake(createPipelineResponse);
this.adjustPayload(pipeline.pipeline);
return pipeline;
}
async findOne(data: IIdRequest) {
try {
const findOnePipelineResponse = await this.pipelineClient.findOne(data);
return objectCamelToSnake(findOnePipelineResponse);
const pipeline = objectCamelToSnake(findOnePipelineResponse);
this.adjustPayload(pipeline.pipeline);
return pipeline;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
@@ -33,7 +44,11 @@ export class PipelinesService {
try {
const findAllPipelineResponse = await this.pipelineClient.findAll(data);
return objectCamelToSnake(findAllPipelineResponse);
const pipelines = objectCamelToSnake(findAllPipelineResponse);
pipelines.forEach((pipeline) => {
this.adjustPayload(pipeline);
});
return { pipelines };
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
@@ -47,7 +62,10 @@ export class PipelinesService {
...data,
});
return objectCamelToSnake(updatePipelineResponse);
const pipeline = objectCamelToSnake(updatePipelineResponse);
this.adjustPayload(pipeline);
return pipeline;
} catch (err) {
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
}
@@ -8,11 +8,33 @@ import {
Put,
} from '@nestjs/common';
import { Payload } from '@nestjs/microservices';
import { ApiTags } from '@nestjs/swagger';
import { TransformationsClientService } from 'src/clients/transformations/client.service';
import { IIdRequest } from 'src/clients/transformations/interfaces';
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
import { Permissions } from '../../authentication/permissions.enum';
import { TransformationsService } from './transformations.service';
@ApiTags('Transformations')
@Controller('transformations')
@AuthenticateCondition((req, user) => {
let action;
switch (req.method) {
case 'POST':
action = 'CREATE';
break;
case 'PUT':
action = 'UPDATE';
break;
default:
action = req.method;
}
return user.permissions.includes(Permissions.TRANSFORMATIONS[action].seqid);
})
export class TransformationsController {
constructor(
private transformationsClientService: TransformationsClientService,
+145
View File
@@ -0,0 +1,145 @@
import { HttpStatus, HttpException, Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import ErrorCodes from './errorCodes';
function enrichErrorCode(code: string) {
switch (code) {
case ErrorCodes.AUTH.WRONG_CREDENTIALS:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não autenticado',
message: 'Usuário ou senha incorretos',
code,
};
case ErrorCodes.AUTH.WRONG_PASSWORD_CONFIRMATION:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Senha incorreta',
message: 'Confirmação de senha incorreta',
code,
};
case ErrorCodes.AUTH.TOTP_NOT_ENABLED:
return {
statusCode: HttpStatus.PRECONDITION_FAILED,
error: 'Não permitido',
message: 'A autenticação multifator não está habilitada',
code,
};
case ErrorCodes.AUTH.TOTP_ALREADY_ENABLED:
return {
statusCode: HttpStatus.PRECONDITION_FAILED,
error: 'Não permitido',
message: 'A autenticação multifator já está habilitada',
code,
};
case ErrorCodes.AUTH.TOTP_ALREADY_DISABLED:
return {
statusCode: HttpStatus.PRECONDITION_FAILED,
error: 'Não permitido',
message: 'A autenticação multifator já está desabilitada',
code,
};
case ErrorCodes.AUTH.TOTP_REQUIRED:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não autenticado',
message: 'Informe o token de autenticação multifator',
code,
};
case ErrorCodes.AUTH.CODE_MISMATCH:
case ErrorCodes.AUTH.CODE_ALREADY_USED:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não autenticado',
message: 'Token de autenticação multifator inválido',
code,
};
case ErrorCodes.AUTH.RESET_PASSWORD_CODE_EXPIRED:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não permitido',
message: 'Token para recuperar senha expirado',
code,
};
case ErrorCodes.AUTH.RESET_PASSWORD_CODE_INVALID:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não permitido',
message: 'Token para recuperar senha inválido',
code,
};
case ErrorCodes.AUTH.WEAK_NEW_PASSWORD:
return {
statusCode: HttpStatus.BAD_REQUEST,
error: 'Não permitido',
message: 'Senha muito fraca. Escolha uma senha mais forte',
code,
};
case ErrorCodes.RATE_LIMIT:
return {
statusCode: HttpStatus.TOO_MANY_REQUESTS,
error: 'Limite excedido',
message:
'Você tentou realizar essa operação muitas vezes. Tente novamente mais tarde',
code,
};
case ErrorCodes.AUTH.UNAUTHORIZED:
return {
statusCode: HttpStatus.UNAUTHORIZED,
error: 'Não autenticado',
message: 'É necessário estar logado para realizar essa operação',
code,
};
case ErrorCodes.AUTH.FORBIDDEN:
return {
statusCode: HttpStatus.FORBIDDEN,
error: 'Não autorizado',
message:
'Você não tem permissões suficientes para realizar essa operação',
code,
};
case ErrorCodes.INTERNAL:
case ErrorCodes.UNKNOWN:
default:
return {
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
error: 'Desconhecido',
message:
'Erro desconhecido. Tente novamente ou entre em contato com o suporte',
code: ErrorCodes.UNKNOWN,
};
}
}
const logger = new Logger();
export default class ErrorBuilder extends HttpException {
code: string;
constructor(code: string | RpcException) {
if (typeof code !== 'string') {
logger.log(code.stack);
code = (code as any).details as string;
} else {
logger.log(code);
}
const { statusCode, message, error, code: rCode } = enrichErrorCode(code);
super({ statusCode, message, error, code: rCode }, statusCode);
this.code = code;
}
}
+40
View File
@@ -0,0 +1,40 @@
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
class OauthSecretsObject {
client_id = '';
client_secret = '';
redirect_uri = '';
}
// @Injectable()
export class OauthSecrets {
hubspot = new OauthSecretsObject();
google = new OauthSecretsObject();
mailchimp = new OauthSecretsObject();
facebook = new OauthSecretsObject();
}
export async function getOauthSecrets() {
const secrets = new OauthSecrets();
const path = process.env.SM_OAUTH_PATH;
const secretsManagerClient = new SecretsManagerClient({});
for (const key in secrets) {
const getSecretComand = new GetSecretValueCommand({
SecretId: `${path}/${key}`,
});
const res = await secretsManagerClient
.send(getSecretComand)
.catch(() => null);
if (res) {
secrets[key] = JSON.parse(res.SecretString);
} else {
secrets[key] = {
client_id: 'id',
client_secret: 'id',
redirect_uri: 'id',
};
}
}
return secrets;
}
+24
View File
@@ -0,0 +1,24 @@
export const AUTH = {
UNAUTHORIZED: 'AUTH.UNAUTHORIZED',
FORBIDDEN: 'AUTH.FORBIDDEN',
WRONG_CREDENTIALS: 'AUTH.WRONG_CREDENTIALS',
WRONG_PASSWORD_CONFIRMATION: 'AUTH.WRONG_PASSWORD_CONFIRMATION',
TOTP_NOT_ENABLED: 'AUTH.TOTP_NOT_ENABLED',
TOTP_ALREADY_ENABLED: 'AUTH.TOTP_ALREADY_ENABLED',
TOTP_ALREADY_DISABLED: 'AUTH.TOTP_ALREADY_DISABLED',
TOTP_REQUIRED: 'AUTH.TOTP_REQUIRED',
CODE_MISMATCH: 'AUTH.CODE_MISMATCH',
CODE_ALREADY_USED: 'AUTH.CODE_ALREADY_USED',
RESET_PASSWORD_CODE_EXPIRED: 'AUTH.RESET_PASSWORD_CODE_EXPIRED',
RESET_PASSWORD_CODE_INVALID: 'AUTH.RESET_PASSWORD_CODE_INVALID',
WEAK_NEW_PASSWORD: 'AUTH.WEAK_NEW_PASSWORD',
};
const ErrorCodes = {
UNKNOWN: 'UNKNOWN',
RATE_LIMIT: 'RATE_LIMIT',
INTERNAL: 'INTERNAL',
AUTH,
};
export default ErrorCodes;
+19
View File
@@ -0,0 +1,19 @@
import { Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { from } from 'rxjs';
import ErrorBuilder from './ErrorBuilder';
const logger = new Logger();
export default async function grpcHandler<T>(method: Promise<T>) {
return new Promise<T>((resolve, reject) => {
from(method).subscribe({
next: resolve,
error: reject,
complete: () => logger.log('done'),
});
}).catch((err: RpcException) => {
throw new ErrorBuilder(err);
});
}
+7
View File
@@ -0,0 +1,7 @@
export const mustache = (string: string, data: Record<string, any> = {}) => {
return Object.entries(data).reduce((res, [key, value]) => {
const mainRe = new RegExp(`(?<!\\\\){{\\s*${key}\\s*}}`, 'g');
const escapeRe = new RegExp(`\\\\({{\\s*${key}\\s*}})`, 'g');
return res.replace(mainRe, value.toString()).replace(escapeRe, '$1');
}, string);
};
+1 -1
View File
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
{
"openapi": "3.0.0",
"paths": {
},
"info": {
"title": "404",
"description": "",
"version": "",
"contact": {}
},
"tags": [],
"servers": [],
"components": {
"schemas": {}
}
}
+2 -1
View File
@@ -17,6 +17,7 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
"noFallthroughCasesInSwitch": false,
"resolveJsonModule": true
}
}