Compare commits

..
Author SHA1 Message Date
Gabriel Amorim a722fc6c86 WIP: new logic for feature request 2023-05-05 12:12:59 -03:00
73 changed files with 11969 additions and 7103 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ module.exports = {
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier', 'plugin:prettier/recommended',
], ],
root: true, root: true,
env: { env: {
+99 -110
View File
@@ -3,7 +3,6 @@ on:
push: push:
branches: branches:
- main - main
- beta
workflow_dispatch: workflow_dispatch:
inputs: inputs:
environment: environment:
@@ -12,12 +11,8 @@ on:
type: choice type: choice
options: options:
- stg - stg
- stg2
- prd - prd
push_to_dockerhub:
description: "Push image to Dockerhub?"
required: true
type: boolean
default: false
jobs: jobs:
extract_environment: extract_environment:
@@ -34,8 +29,6 @@ jobs:
echo "environment=${DEPLOY_ENV}" >> $GITHUB_OUTPUT echo "environment=${DEPLOY_ENV}" >> $GITHUB_OUTPUT
elif [ ${GITHUB_REF} == "refs/heads/main" ]; then elif [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "environment=prd" >> $GITHUB_OUTPUT echo "environment=prd" >> $GITHUB_OUTPUT
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "environment=stg" >> $GITHUB_OUTPUT
fi fi
id: extract_environment id: extract_environment
@@ -46,7 +39,7 @@ jobs:
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }} 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: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- if: github.event_name != 'workflow_dispatch' - if: github.event_name != 'workflow_dispatch'
name: Semantic Release name: Semantic Release
@@ -54,43 +47,45 @@ jobs:
id: semantic id: semantic
with: with:
extra_plugins: | extra_plugins: |
conventional-changelog-eslint@4.0.0 conventional-changelog-eslint
branches: | branches: |
[ [
'main', 'main'
{
name: 'alpha',
prerelease: true
},
{
name: 'beta',
prerelease: true
}
] ]
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_ecr_image: deploy-info:
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }} if: ${{ github.event_name == 'workflow_dispatch'}}
needs: [extract_environment, semantic_release] needs: [extract_environment, semantic_release]
runs-on: runs-on: ubuntu-latest
[self-hosted, "prd"]
steps: steps:
- name: Printing stats - name: Create summary
env: env:
EVENT: ${{ github.event_name }} EVENT: ${{ github.event_name }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }} IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ENV: ${{ needs.extract_environment.outputs.environment }} ENV: ${{ needs.extract_environment.outputs.environment }}
run: echo ${GITHUB_REF#refs/heads/} run: echo "### Deploy da branch \`$GITHUB_REF_NAME\` no ambiente **$ENV** :rocket:" >> $GITHUB_STEP_SUMMARY
deploy:
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
needs: [extract_environment, semantic_release]
runs-on:
[self-hosted, "${{ needs.extract_environment.outputs.environment }}"]
steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Update Pip - name: Update Pip
run: | run: |
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
- name: Install Docker Compose
run: |
python3 -m pip install docker-compose --upgrade
- name: Install AWS CLI - name: Install AWS CLI
run: | run: |
python3 -m pip install awscli --upgrade python3 -m pip install awscli --upgrade
@@ -100,14 +95,14 @@ jobs:
python3 -m pip install awsebcli --upgrade python3 -m pip install awsebcli --upgrade
- name: Configure AWS Region - name: Configure AWS Region
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v1-node16
id: aws id: aws
with: with:
aws-region: us-east-1 aws-region: us-east-1
- name: Login to AWS ECR - name: Login to AWS ECR
id: login_ecr id: login_ecr
uses: aws-actions/amazon-ecr-login@v2 uses: aws-actions/amazon-ecr-login@v1
- name: Build, Tag, and Push Image to AWS ECR - name: Build, Tag, and Push Image to AWS ECR
env: env:
@@ -115,103 +110,97 @@ jobs:
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }} IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }} ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: | run: |
docker compose -f build.docker-compose.yml build docker-compose -f build.docker-compose.yml build
docker compose -f build.docker-compose.yml push docker-compose -f build.docker-compose.yml push
- name: Login to Docker Hub - name: Create ZIP file to Deploy AWS Beanstalk
if: ${{inputs.push_to_dockerhub}}
uses: docker/login-action@v2
with:
username: dadosfera
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build, Tag, and Push Image to Dockerhub
if: ${{inputs.push_to_dockerhub}}
env: env:
ENV: ${{ needs.extract_environment.outputs.environment }} ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }} IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }} ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
NODE_EXPORTER_URL: ${{needs.extract_environment.outputs.environment == 'prd' && '611330257153.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest' || '468720548566.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest'}}
run: | run: |
docker compose -f build.docker-compose.dockerhub.yml build sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
docker compose -f build.docker-compose.dockerhub.yml push sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
sed -i -e "s/\${NODE_EXPORTER_URL}/$NODE_EXPORTER_URL/g" docker-compose.yml
zip deploy.zip docker-compose.yml -r .ebextensions
# - name: Create ZIP file to Deploy AWS Beanstalk - name: Deploy AWS Beanstalk
# env: env:
# ENV: ${{ needs.extract_environment.outputs.environment }} ENV: ${{ needs.extract_environment.outputs.environment }}
# IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }} AWS_REGION: us-east-1
# ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }} APP_NAME: ${{ github.event.repository.name }}
# NODE_EXPORTER_URL: ${{needs.extract_environment.outputs.environment == 'prd' && '611330257153.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest' || '468720548566.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest'}} run: |
# run: | eb use $APP_NAME-$ENV
# sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
# sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml eb deploy
# sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
# sed -i -e "s/\${NODE_EXPORTER_URL}/$NODE_EXPORTER_URL/g" docker-compose.yml
# zip deploy.zip docker-compose.yml -r .ebextensions
# - name: Deploy AWS Beanstalk
# env:
# ENV: ${{ needs.extract_environment.outputs.environment }}
# AWS_REGION: us-east-1
# APP_NAME: ${{ github.event.repository.name }}
# run: |
# eb use $APP_NAME-$ENV
# echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
# eb deploy
- name: Remove Docker's Trash - name: Remove Docker's Trash
if: always() if: always()
run: | run: |
docker system prune --volumes -a -f docker system prune --volumes -a -f
docker system df docker system df
api_docs:
helmfile-deploy: needs: [extract_environment, semantic_release, deploy]
needs: [extract_environment, semantic_release, build_ecr_image] runs-on: ubuntu-latest
runs-on: [self-hosted, "prd-azure"]
steps: steps:
- name: Checkout code - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set up Helm - name: Extract Docs BlockId and PageId
uses: azure/setup-helm@v1
with:
version: 'v3.9.0'
- name: Install Azure ClI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
- uses: azure/login@v2
with:
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install Helmfile
run: |
wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz
tar -xzf helmfile_0.148.0_linux_amd64.tar.gz
mv helmfile /usr/local/bin/
helmfile --version
- name: Install Helm Diff Plugin
run: helm plugin install https://github.com/databus23/helm-diff || true
- name: Setup kubectl
uses: azure/setup-kubectl@v1
with:
version: 'v1.30.1'
- name: Authenticate with cluster
env:
CLUSTER_NAME: platform-${{ needs.extract_environment.outputs.environment }}
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
- name: Run Helmfile Apply
env: env:
ENV: ${{ needs.extract_environment.outputs.environment }} ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }} STG2_DOCS_BLOCK_ID: ${{ secrets.DEV_DOCS_BLOCK_ID }}
run: helmfile -f helmfiles/${ENV}.yaml sync --set image.tag=$IMAGE_TAG STG2_DOCS_PAGE_ID: ${{ secrets.DEV_DOCS_PAGE_ID }}
STG_DOCS_BLOCK_ID: ${{ secrets.STG_DOCS_BLOCK_ID }}
STG_DOCS_PAGE_ID: ${{ secrets.STG_DOCS_PAGE_ID }}
PRD_DOCS_BLOCK_ID: ${{ secrets.PRD_DOCS_BLOCK_ID }}
PRD_DOCS_PAGE_ID: ${{ secrets.PRD_DOCS_PAGE_ID }}
shell: bash
run: |
if [ $ENV == "stg2" ]; then
echo "block_id=$STG2_DOCS_BLOCK_ID" >> $GITHUB_OUTPUT
echo "page_id=$STG2_DOCS_PAGE_ID" >> $GITHUB_OUTPUT
elif [ $ENV == "stg" ]; then
echo "block_id=$STG_DOCS_BLOCK_ID" >> $GITHUB_OUTPUT
echo "page_id=$STG_DOCS_PAGE_ID" >> $GITHUB_OUTPUT
elif [ $ENV == "prd" ]; then
echo "block_id=$PRD_DOCS_BLOCK_ID" >> $GITHUB_OUTPUT
echo "page_id=$PRD_DOCS_PAGE_ID" >> $GITHUB_OUTPUT
fi
id: extract_docs_info
- name: Generate API docs
env:
DOCS_URL: ${{ secrets.DOCS_URL }}
DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }}
DOCS_PAGE_ID: ${{ steps.extract_docs_info.outputs.page_id }}
DOCS_BLOCK_ID: ${{ steps.extract_docs_info.outputs.block_id }}
EVENT: ${{ github.event_name }}
RELEASE_VERSION: ${{ needs.semantic_release.outputs.new_release_version }}
run: |
if [ ${EVENT} != "workflow_dispatch" ]; then
sed -i -E "s/(\"title\": )\"Maestro.+\"/\1\"Maestro - $RELEASE_VERSION\"/g" docsfera.json
fi
sed -i -e "s/\${DOCS_API_TOKEN}/$DOCS_API_TOKEN/g" docsfera.json
sed -i -e "s/\${DOCS_PAGE_ID}/$DOCS_PAGE_ID/g" docsfera.json
sed -i -e "s/\${DOCS_BLOCK_ID}/$DOCS_BLOCK_ID/g" docsfera.json
curl -X POST -H 'Content-Type: application/json' -d @docsfera.json $DOCS_URL
- name: Generate External API docs
env:
DOCS_URL: ${{ secrets.DOCS_URL }}
DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }}
DOCS_PAGE_ID: ${{secrets.EXTERNAL_DOCS_PAGE_ID}}
DOCS_BLOCK_ID: ${{secrets.EXTERNAL_DOCS_BLOCK_ID}}
EVENT: ${{ github.event_name }}
RELEASE_VERSION: ${{ needs.semantic_release.outputs.new_release_version }}
run: |
if [ ${EVENT} != "workflow_dispatch" ]; then
sed -i -E "s/(\"title\": )\"Maestro.+\"/\1\"Maestro - $RELEASE_VERSION\"/g" docsfera.external.json
fi
sed -i -e "s/\${DOCS_API_TOKEN}/$DOCS_API_TOKEN/g" docsfera.external.json
sed -i -e "s/\${DOCS_PAGE_ID}/$DOCS_PAGE_ID/g" docsfera.external.json
sed -i -e "s/\${DOCS_BLOCK_ID}/$DOCS_BLOCK_ID/g" docsfera.external.json
curl -X POST -H 'Content-Type: application/json' -d @docsfera.external.json $DOCS_URL
+11 -6
View File
@@ -6,18 +6,23 @@ on:
jobs: jobs:
test: test:
runs-on: [self-hosted, prd] runs-on: self-hosted
env:
APP_NAME: ${{ github.event.repository.name }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Build - name: Build
run: docker build -t ${APP_NAME}_teste --target test . env:
APP_NAME: ${{ github.event.repository.name }}
run: |
docker build -t $APP_NAME --target test .
- name: Run Test - name: Run Test
run: docker run ${APP_NAME}_teste env:
ENV: test
APP_NAME: ${{ github.event.repository.name }}
run: |
docker run --rm --entrypoint="npm" $APP_NAME run test
- name: Remove Docker's Trash - name: Remove Docker's Trash
if: always() if: always()
-79
View File
@@ -1,79 +0,0 @@
name: Validate K8S Modifications
on:
pull_request:
branches:
- main
- stg
jobs:
extract_environment:
runs-on: ubuntu-22.04
outputs:
environment: ${{ steps.extract_environment.outputs.environment }}
steps:
- name: Extract Environment
run: |
if [ "${{ github.event.pull_request.base.ref }}" == "main" ]; then
echo "environment=prd" >> $GITHUB_OUTPUT
elif [ "${{ github.event.pull_request.base.ref }}" == "beta" ]; then
echo "environment=stg" >> $GITHUB_OUTPUT
fi
id: extract_environment
helmfile-deploy:
needs: [extract_environment]
runs-on: [self-hosted, "prd-azure"]
steps:
- name: Summary
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
run: |
echo "### :rocket: Deploy da branch \`$GITHUB_REF_NAME\` para o environment ($ENV)" >> $GITHUB_STEP_SUMMARY
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: 'v3.9.0'
- name: Install Azure ClI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
- uses: azure/login@v2
with:
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install Helmfile
run: |
wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz
tar -xzf helmfile_0.148.0_linux_amd64.tar.gz
mv helmfile /usr/local/bin/
helmfile --version
- name: Install Helm Diff Plugin
run: helm plugin install https://github.com/databus23/helm-diff || true
- name: Setup kubectl
uses: azure/setup-kubectl@v1
with:
version: 'v1.30.1'
- name: Authenticate with cluster
env:
CLUSTER_NAME: platform-${{ needs.extract_environment.outputs.environment }}
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
- name: Run Helmfile Diff
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
run: helmfile -f helmfiles/${ENV}.yaml diff
+1 -1
View File
@@ -1 +1 @@
18.17 18.3.0
-1
View File
@@ -12,7 +12,6 @@
"start:debug" "start:debug"
], ],
"runtimeExecutable": "npm", "runtimeExecutable": "npm",
"runtimeVersion": "18.17",
"skipFiles": [ "skipFiles": [
"<node_internals>/**" "<node_internals>/**"
], ],
+17 -39
View File
@@ -1,43 +1,21 @@
FROM node:18.17-alpine AS base_image # install all dependencies
FROM node:18.3.0-alpine3.15 as packages
FROM base_image AS build_base
WORKDIR /app WORKDIR /app
RUN apk update COPY package.json package-lock.json ./
# needed packages to build dependencies from source RUN apk add --no-cache aws-cli \
RUN apk add --no-cache aws-cli && aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \
COPY package*.json ./ && npm install
# run aws cli without mounting secret, because CI already has AWS credentials
FROM build_base AS ci_image
RUN aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1
RUN npm ci
COPY . .
# unit test specific build # unit test specific build
FROM ci_image AS test FROM node:18.3.0-alpine3.15 as test
ENV DUC_URL=0.0.0.0:50051
ENTRYPOINT ["npm", "run", "test"]
# dev build
FROM build_base AS dev
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1
# flag --build-from-source is required to force-build sqlite3
RUN npm ci
COPY . .
ENTRYPOINT npm run start:dev
FROM ci_image AS prod_build
RUN npm run build
FROM base_image
WORKDIR /app WORKDIR /app
COPY --from=prod_build /app/dist ./dist COPY --from=packages /app/node_modules ./node_modules
COPY --from=prod_build /app/node_modules ./node_modules COPY . ./
COPY --from=prod_build /app/package*.json ./
ENTRYPOINT npm run start:prod FROM node:18.3.0-alpine3.15
WORKDIR /app
COPY . /app/
COPY --from=packages /app/node_modules ./node_modules
RUN npm run build
EXPOSE 3333
ENTRYPOINT npm run start
-10
View File
@@ -1,10 +0,0 @@
LICENSE
Copyright (C) 2023 Dadosfera
All rights reserved.
This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any means.
Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.
If you have any questions about this, please contact Dadosfera.ai at legal@dadosfera.ai
+84 -88
View File
@@ -2,57 +2,52 @@
<image src="./assets/maestro.svg" style="width:10rem"> <image src="./assets/maestro.svg" style="width:10rem">
</p> </p>
# Maestro # Maestro
Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços. Maestro is the Dadosfera's gateway, it's responsible for the communication between the frontend application and Dadosfera's microservices.
```mermaid ## 🚀 Starting
graph TD;
Frontend<-->Maestro;
Maestro<-->duc;
Maestro<-->pi-factory;
Maestro<-->in-factory;
```
É uma API REST, desenvolvida em NodeJs utilizando o Framework [NestJs](https://docs.nestjs.com/). These instructions will allow you to get a working copy of the project on your local machine for development and testing purposes.
## 🚀 Iniciando ### 📋 Requirements
Estas instruções permitirão que você obtenha uma cópia funcional do projeto em sua máquina local para desenvolvimento e testes. - [NodeJS v18.3.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
- Request access to AWS Console dev account for **all services** (avoid gradually asking for each needed service. it will slow down your development cycle)
- Create your Access Key on the "Security credentials" menu
- Set the Access Key on your local development machine
- Request access to the dev, stg and prd VPNs
### 📋 Requisitos ### 🔧 Installation<a id="installation"></a>
- [NodeJS v18.17 / NPM v9.6.7](https://nodejs.org/pt-br/download/) - Clone the repository
> Dica: Utilize [NVM](https://github.com/nvm-sh/nvm) para gerenciar facilmente as versões do node - SSH
- Solicite acesso à conta de desenvolvimento do AWS Console para **todos os serviços necessários** ```
- Crie sua Access Key no menu "Security credentials" git clone git@github.com:dadosfera/maestro.git
- Defina a Access Key em sua máquina de desenvolvimento local ```
- Solicite acesso às VPNs de stg e prd
### 🔧 Instalação<a id="installation"></a> or
- Clone o repositório - HTTPS
```
git clone https://github.com/dadosfera/maestro.git
```
```sh - Select the correct node version (optional, only if using [NVM](https://github.com/nvm-sh/nvm)):
git clone git@github.com:dadosfera/maestro.git
```
- Selecione a versão correta do node (opcional, apenas se estiver usando o [NVM](https://github.com/nvm-sh/nvm)):
```sh ```sh
nvm use nvm use
``` ```
- Instale as dependências do projeto: - Install the project dependencies:
```sh ```sh
npm i npm i
``` ```
- Configure as seguintes variáveis de ambiente: - Setup the following enviroment variables:
``` ```
ENV= ENV=
@@ -63,41 +58,42 @@ Estas instruções permitirão que você obtenha uma cópia funcional do projeto
SM_OAUTH_PATH= SM_OAUTH_PATH=
``` ```
- Inicie o servidor: - Start the server:
```sh ```sh
# dev mode
npm run start:dev npm run start:dev
# or in debug mode
npm run start:debug
``` ```
> Se preferir, utilize o debbuger do VSCode apertando F5 The service should start successfully.
O serviço deve iniciar com sucesso. ## 📄 Documentation
## 📄 Documentação NestJs makes it easy to document each route using decorators on all requests and responses properties. It automatically generates a swagger for given information and provides a route to access it http://localhost:3333/api.
For more info check the [official documentation](https://docs.nestjs.com/openapi/introduction).
O NestJs facilita a documentação de cada rota usando decoradores em todas as propriedades de solicitações e respostas. Ele gera automaticamente um swagger para as informações fornecidas e fornece uma rota para acessá-lo em http://localhost:3333/api. Para mais informações, consulte a [documentação oficial](https://docs.nestjs.com/openapi/introduction). ### - Multiple documentations
### - Documentações múltiplas We are currently generating **2 different** documentations: Internal and External.
Atualmente, estamos gerando **2 documentações diferentes**: Interna e Externa. All routes that have the decorator `@ApiInternalOnly()` will not be visible on the **External** API swagger.
Todas as rotas que têm o decorador `@ApiInternalOnly()` não serão visíveis no swagger da API **Externa**. - When you start the application with `npm run start` it will serve and generate the swagger JSON of the **External** API
- When you start the application with `npm run start:internal` it will serve and generate the swagger JSON of the **Internal** API
- When you run `npm run docs` it will generate the swagger JSON of both **Internal** and **External** API, and save them to `docsfera.json` and `docsfera.external.json` respectively;
- Quando você inicia a aplicação com `npm run start:dev`, ela servirá e gerará o JSON do swagger da API **Interna** It is important to run `npm run docs` before every deploy so we can always have the most updated docs published.
- Quando você executa `npm run docs`, ele gerará o JSON do swagger de ambas as APIs **Interna** e **Externa** e os salvará em `docsfera.json` e `docsfera.external.json`, respectivamente; ## Authentication decorators
> Link para documentação interna: https://dadosfera.github.io/docsfera Maestro have utilities to ease the user authentication on every controller and route. The following decorators are available:
É importante executar `npm run docs` antes de cada implantação para que sempre tenhamos as documentações mais atualizadas publicadas.
## Decoradores de autenticação
O Maestro possui utilitários para facilitar a autenticação do usuário em todos os controladores e rotas. Os seguintes decoradores estão disponíveis:
### `@Authenticated` ### `@Authenticated`
Se o usuário precisar estar autenticado para fazer uma solicitação, podemos usar o decorador `@Authenticated` no controlador ou rota, conforme necessário. If the user must be authenticated to make request, we can use the `@Authenticated` decorator in the controller or route, as needed.
```ts ```ts
import { Authenticated } from '../../authentication/authentication.decorator'; import { Authenticated } from '../../authentication/authentication.decorator';
@@ -109,7 +105,7 @@ class FooController {
@Get('bar') @Get('bar')
async getBar() { async getBar() {
this.logger.info('usuário está autenticado!'); this.logger.info('user is authenticated!');
return { authenticated: true }; return { authenticated: true };
} }
@@ -126,14 +122,14 @@ class FooController {
@Get('bar') @Get('bar')
@Authenticated() @Authenticated()
async getBar() { async getBar() {
this.logger.info('usuário está autenticado!'); this.logger.info('user is authenticated!');
return { authenticated: true }; return { authenticated: true };
} }
@Post('bar') @Post('bar')
async postBar() { async postBar() {
this.logger.info('usuário NÃO está autenticado!'); this.logger.info('user is NOT authenticated!');
return { authenticated: false }; return { authenticated: false };
} }
@@ -142,7 +138,7 @@ class FooController {
### `@RequireAllPermissions` ### `@RequireAllPermissions`
Este decorador exige que **todas as permissões** listadas sejam concedidas ao usuário solicitante. This decorator requires that **all permissions** listed are granted to the requesting user.
```ts ```ts
import { RequireAllPermissions } from '../../authentication/authentication.decorator'; import { RequireAllPermissions } from '../../authentication/authentication.decorator';
@@ -162,7 +158,7 @@ class FooController {
### `@RequireSomePermission` ### `@RequireSomePermission`
No caso seguinte, é necessário que o usuário tenha **pelo menos uma** das permissões listadas. In the following case, the user is required to have **at least one** listed permission.
```ts ```ts
import { RequireSomePermission } from '../../authentication/authentication.decorator'; import { RequireSomePermission } from '../../authentication/authentication.decorator';
@@ -182,18 +178,18 @@ class FooController {
### `@AuthenticateCondition` ### `@AuthenticateCondition`
Se for necessária uma verificação de autenticação mais complicada, podemos usar o decorador `@AuthenticateCondition` para defini-la. A função personalizada deve retornar `true` para autenticar a solicitação. If a more complicated authentication check needs to be done, we can use the `@AuthenticateCondition` decorator to define it. The custom function must return `true` to authenticate the request.
No exemplo a seguir: In the following example:
- todas as rotas no controlador `FooController` só podem ser solicitadas a partir do localhost - all routes on the `FooController` controller can only be requested from localhost
- `POST /foo/bar` só pode ser solicitado a partir do localhost **e** por usuários do cliente com id `111...eef` - `POST /foo/bar` can only be requested from localhost **and** by users from customer id `111...eef`
```ts ```ts
import { AuthenticateCondition } from '../../authentication/authentication.decorator'; import { AuthenticateCondition } from '../../authentication/authentication.decorator';
@Controller('foo') @Controller('foo')
// permitir solicitações apenas do localhost // allow requests only from localhost
@AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1') @AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1')
class FooController { class FooController {
/* ... */ /* ... */
@@ -204,7 +200,7 @@ class FooController {
} }
@Post('bar') @Post('bar')
// permitir solicitações apenas de um cliente específico // allow requests only from a specific customer
@AuthenticateCondition( @AuthenticateCondition(
(request: Request, user: RequestUser) => (request: Request, user: RequestUser) =>
user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef', user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef',
@@ -215,11 +211,11 @@ class FooController {
} }
``` ```
### Nota sobre decoradores de autenticação ### Note on authentication decorators
- O método antigo de autenticação colocava os dados do usuário no campo `request.body.info`, o que impõe certos problemas em relação ao corpo da solicitação, pois esses dados devem vir do frontend sem qualquer modificação pelo Maestro. Agora, esse uso está ⚠️ **DESCONTINUADO** ⚠️. Estamos trabalhando para migrar para o decorador de parâmetro `@User`. O método antigo está funcionando enquanto a migração está em andamento. - The old authentication method placed the user data in the `request.body.info` field, this imposes certain issues regarding the request body because this data should be from the frontend without any modification by Maestro. Now this usage is ⚠️ **DEPRECATED** ⚠️. We are working to migrate to the `@User` parameter decorator. The old method is working while the migration is in progress.
- Os decoradores de autenticação podem ser usados juntos e todos eles **devem** passar para que a solicitação seja autenticada, mas, no caso geral, você não precisa (_e não gostaria de..._) usar todos eles juntos, pois você pode codificar toda a lógica de autenticação no decorador `@AuthenticateCondition`. - Authentication decorators can be used together and all of them **must** pass to the request be authenticated, but in the general case you don't need to (_and wouldn't like to..._) use all of them together, as you can code all of the authentication logic in the `@AuthenticateCondition` decorator.
```ts ```ts
import { import {
@@ -255,17 +251,17 @@ class FooController {
} }
``` ```
- Se `@RequireAllPermissions` e `@RequireSomePermission` forem usados com **apenas uma única permissão**, eles apresentam o **exatamente mesmo comportamento**. - If `@RequireAllPermissions` and `@RequireSomePermission` are used with **only a single permission**, they present the **exactly same behavior**.
```ts ```ts
// mesmo comportamento // same behavior
@RequireAllPermissions(Permissions.BAR.MANAGE) @RequireAllPermissions(Permissions.BAR.MANAGE)
@RequireSomePermission(Permissions.BAR.MANAGE) @RequireSomePermission(Permissions.BAR.MANAGE)
``` ```
## Decorador de parâmetro `@User` ## `@User` parameter decorator
Os dados do usuário solicitante podem ser obtidos usando o decorador de parâmetro @User, como no exemplo a seguir: The requesting user data can be obtained using the @User parameter decorator, like in the following snippet:
```ts ```ts
import { User, RequestUser } from '../../authentication/user.decorator'; import { User, RequestUser } from '../../authentication/user.decorator';
@@ -283,7 +279,7 @@ class FooController {
} }
``` ```
Se o usuário precisar estar logado, defina `required` como `true`, como por exemplo: If the user is required to be logged in, set `required` to `true`, as you would want in the `change-password` operation:
```ts ```ts
import { User, RequestUser } from '../../authentication/user.decorator'; import { User, RequestUser } from '../../authentication/user.decorator';
@@ -309,46 +305,46 @@ class UserController {
} }
``` ```
## 📦 Desenvolvimento ## 📦 Development
### ⌨️ Estilo de Codificação ### ⌨️ Coding Style
Por padrão, usamos [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) com configurações padrão. By default, we use [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) with default settings.
**Recomendamos usar o Visual Studio Code e instalar as extensões recomendadas para facilitar o processo de desenvolvimento.** **We recommend using Visual Studio Code and installing the recommended extensions to ease the development process.**
### Padrão de commits ### Commits pattern
Nosso pipeline de fluxo de trabalho segue as especificações de commits convencionais ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) para liberar versões conforme necessário. Our workflow pipeline follows the conventional commits specs ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) to release versions accordingly.
Formato: `<type>[optional scope]: <description>` Format: `<type>[optional scope]: <description>`
Exemplo: `FIX: ensure Range headers adhere more closely to RFC 2616` Example: `FIX: ensure Range headers adhere more closely to RFC 2616`
### Convenção de nomenclatura de branchs ### Branching naming convention
- **Feature**: Quaisquer alterações de código para um novo módulo ou caso de uso devem ser feitas em uma branch de feature. Esta branch é criada com base na branch `main`. Quando todas as alterações estiverem concluídas, será necessário um Pull Request/Merge Request para colocar todas essas alterações de volta na branch `main`. Exemplos: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`. - **Feature**: Any code changes for a new module or use case should be done on a feature branch. This branch is created based on the `main` branch. When all changes are done, a Pull Request/Merge Request is needed to put all of these changes back to the `main` branch. Examples: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`.
**Recomenda-se usar todas as letras em minúsculas e hífen (-) para separar palavras, a menos que seja um nome ou ID de item específico. O sublinhado (\_) pode ser usado para separar o ID e a descrição.** **It is recommended to use all lower caps letters and hyphen (-) to separate words unless it is a specific item name or ID. Underscore (\_) could be used to separate the ID and description.**
- **Bug Fix**: Se as alterações de código feitas na branch de feature foram rejeitadas após um lançamento, sprint ou demo, quaisquer correções necessárias após isso devem ser feitas na branch de correção de bug. Exemplos: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`. - **Bug Fix**: If the code changes made from the feature branch were rejected after a release, sprint or demo, any necessary fixes after that should be done on the bugfix branch. Examples: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`.
- **Hot Fix**: Se houver necessidade de corrigir um bloqueador, fazer um patch temporário, aplicar uma mudança crítica de framework ou configuração que deva ser tratada imediatamente, ela deve ser criada como um Hotfix. Exemplos: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`. - **Hot Fix**: If there is a need to fix a blocker, do a temporary patch, apply a critical framework or configuration change that should be handled immediately, it should be created as a Hotfix. Examples: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`.
- **Experimental**: Uma branch para experimentar. Qualquer nova feature ou ideia que não faça parte de um lançamento ou sprint. Exemplo: `experimental/dark-theme-support`. - **Experimental**: A branch for playing around. Any new feature or idea that is not part of a release or a sprint. Example: `experimental/dark-theme-support`.
### Fazendo um Pull Request ### Making a Pull Request
1. Comite suas alterações 1. Commit your changes
2. Abra o Pull Request no GitHub 2. Open the Pull Request on GitHub
3. Envie o link do Pull Request no grupo de chat do Google da Microsfera para revisão e possível aprovação 3. Send Pull Request link in Microsfera Google Chat Group for review and possible approval
## 🛠️ Construído com ## 🛠️ Built with
Algumas tecnologias usadas neste projeto: Some technologies used in this project:
- [NestJS](https://docs.nestjs.com) - Framework para construir aplicações NodeJS eficientes e escaláveis no lado do servidor - [NestJS](https://docs.nestjs.com) - Framework for building efficient and scalable NodeJS server-side applications
## ⚙️ Arquitetura de Back-end ## ⚙️ Back-end Architecture
A arquitetura pode ser encontrada neste [link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end). The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).
-4
View File
@@ -1,4 +0,0 @@
services:
maestro:
build: .
image: dadosfera/maestro_${ENV}:${IMAGE_TAG}
+2 -1
View File
@@ -1,4 +1,5 @@
version: "3.8"
services: services:
maestro: maestro:
build: . build: .
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_prd:${IMAGE_TAG} image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
-28
View File
@@ -1,28 +0,0 @@
services:
maestro:
image: dadosfera/maestro_${ENV}:${IMAGE_TAG}
container_name: maestro
restart: always
ports:
- 3333:3333
env_file:
- .env
node_exporter:
image: ${NODE_EXPORTER_URL}
container_name: node_exporter
restart: always
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
- /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
- '--collector.systemd'
- '--collector.processes'
network_mode: host
pid: host
+1
View File
@@ -1,3 +1,4 @@
version: "3.8"
services: services:
maestro: maestro:
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG} image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
+2 -231
View File
@@ -133,32 +133,7 @@
}, },
"get": { "get": {
"operationId": "ConnectionController_getAllConnections", "operationId": "ConnectionController_getAllConnections",
"parameters": [ "parameters": [],
{
"name": "search",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "size",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "", "description": "",
@@ -515,45 +490,6 @@
] ]
} }
}, },
"/pipelinesV2/monitoring-dashboard": {
"get": {
"operationId": "PipelinesController_getMonitoringDashboard",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"PipelinesV2"
],
"security": [
{
"access-token": []
}
]
}
},
"/pipelinesV2": { "/pipelinesV2": {
"post": { "post": {
"operationId": "PipelinesController_create", "operationId": "PipelinesController_create",
@@ -1800,118 +1736,6 @@
] ]
} }
}, },
"/customers/{id}/links": {
"get": {
"operationId": "CustomersController_getCustomerLinks",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerLinksResponse"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
}
]
},
"put": {
"operationId": "CustomersController_setCustomerLinks",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerLinkRequest"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/customers/token": {
"get": {
"operationId": "CustomersController_getCustomerToken",
"parameters": [
{
"name": "exp",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/health": { "/health": {
"get": { "get": {
"operationId": "HealthController_check", "operationId": "HealthController_check",
@@ -1928,7 +1752,7 @@
} }
}, },
"info": { "info": {
"title": "Maestro - feat/new-customer-monitoring", "title": "Maestro - feature/customer-links",
"description": "This is the Maestro API", "description": "This is the Maestro API",
"version": "1.0.0", "version": "1.0.0",
"contact": {} "contact": {}
@@ -2209,9 +2033,6 @@
"ConnectionToCatalogDto": { "ConnectionToCatalogDto": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@@ -3303,56 +3124,6 @@
"updated_at", "updated_at",
"updated_by" "updated_by"
] ]
},
"CustomerLink": {
"type": "object",
"properties": {
"href": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"iconSrc": {
"type": "string"
}
},
"required": [
"href",
"name",
"description"
]
},
"CustomerLinksResponse": {
"type": "object",
"properties": {
"links": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CustomerLink"
}
}
},
"required": [
"links"
]
},
"CustomerLinkRequest": {
"type": "object",
"properties": {
"links": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CustomerLink"
}
}
},
"required": [
"links"
]
} }
} }
} }
+73 -630
View File
@@ -582,32 +582,7 @@
}, },
"get": { "get": {
"operationId": "ConnectionController_getAllConnections", "operationId": "ConnectionController_getAllConnections",
"parameters": [ "parameters": [],
{
"name": "search",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "page",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "size",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "", "description": "",
@@ -1437,6 +1412,51 @@
] ]
} }
}, },
"/users/connector_request": {
"post": {
"operationId": "UsersController_sendConnectorRequest",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateUserRes"
}
}
}
},
"201": {
"description": ""
}
},
"tags": [
"Users"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/roles": { "/roles": {
"get": { "get": {
"operationId": "RolesController_searchRoles", "operationId": "RolesController_searchRoles",
@@ -2688,45 +2708,6 @@
] ]
} }
}, },
"/pipelinesV2/monitoring-dashboard": {
"get": {
"operationId": "PipelinesController_getMonitoringDashboard",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"PipelinesV2"
],
"security": [
{
"access-token": []
}
]
}
},
"/pipelinesV2": { "/pipelinesV2": {
"post": { "post": {
"operationId": "PipelinesController_create", "operationId": "PipelinesController_create",
@@ -4513,280 +4494,6 @@
] ]
} }
}, },
"/catalog/rls-rule": {
"post": {
"operationId": "CatalogController_addRlsRule",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
},
"get": {
"operationId": "CatalogController_getRlsRules",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
},
"delete": {
"operationId": "CatalogController_batchRemoveRlsRules",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "nimbus_dashboard_id",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "id_rls",
"required": false,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/catalog/rls-rule/{id}": {
"get": {
"operationId": "CatalogController_getOneRlsRule",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
},
"delete": {
"operationId": "CatalogController_removeRlsRule",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/catalog/nimbus-dashboards": {
"get": {
"operationId": "CatalogController_getNimbusDashboards",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Catalog"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/terms-of-use/token": { "/terms-of-use/token": {
"get": { "get": {
"operationId": "TermsOfUseController_getToken", "operationId": "TermsOfUseController_getToken",
@@ -5261,14 +4968,7 @@
}, },
"responses": { "responses": {
"201": { "201": {
"description": "", "description": ""
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
} }
}, },
"tags": [ "tags": [
@@ -5300,16 +5000,7 @@
"/customers/{id}/links": { "/customers/{id}/links": {
"get": { "get": {
"operationId": "CustomersController_getCustomerLinks", "operationId": "CustomersController_getCustomerLinks",
"parameters": [ "parameters": [],
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "", "description": "",
@@ -5326,6 +5017,9 @@
"Customers" "Customers"
], ],
"security": [ "security": [
{
"access-token": []
},
{ {
"access-token": [] "access-token": []
} }
@@ -5333,89 +5027,27 @@
}, },
"put": { "put": {
"operationId": "CustomersController_setCustomerLinks", "operationId": "CustomersController_setCustomerLinks",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerLinkRequest"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/customers/token": {
"get": {
"operationId": "CustomersController_getCustomerToken",
"parameters": [
{
"name": "exp",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/customers/monitoring-dashboard": {
"get": {
"operationId": "CustomersController_getCustomerMonitoringDashboard",
"parameters": [], "parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerDto"
}
}
}
},
"responses": { "responses": {
"200": { "200": {
"description": "" "description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
} }
}, },
"tags": [ "tags": [
@@ -5431,117 +5063,6 @@
] ]
} }
}, },
"/open-data/sharing-ocean-data": {
"post": {
"operationId": "OpenDataController_createUser",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "language",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"OpenData"
]
}
},
"/customers/{id}/theme": {
"post": {
"operationId": "ThemeController_saveCustomertheme",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerThemeRequest"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerThemeResponse"
}
}
}
}
},
"tags": [
"Theme"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
},
"get": {
"operationId": "ThemeController_getCustomerTheme",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerThemeResponse"
}
}
}
}
},
"tags": [
"Theme"
]
}
},
"/health": { "/health": {
"get": { "get": {
"operationId": "HealthController_check", "operationId": "HealthController_check",
@@ -5558,7 +5079,7 @@
} }
}, },
"info": { "info": {
"title": "Maestro", "title": "Maestro - main",
"description": "This is the Maestro API", "description": "This is the Maestro API",
"version": "1.0.0", "version": "1.0.0",
"contact": {} "contact": {}
@@ -5834,9 +5355,6 @@
"ConnectionToCatalogDto": { "ConnectionToCatalogDto": {
"type": "object", "type": "object",
"properties": { "properties": {
"id": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@@ -8296,35 +7814,13 @@
"tags" "tags"
] ]
}, },
"CustomerLink": {
"type": "object",
"properties": {
"href": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"iconSrc": {
"type": "string"
}
},
"required": [
"href",
"name",
"description"
]
},
"CustomerLinksResponse": { "CustomerLinksResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"links": { "links": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/components/schemas/CustomerLink" "type": "string"
} }
} }
}, },
@@ -8332,72 +7828,19 @@
"links" "links"
] ]
}, },
"CustomerLinkRequest": { "CustomerDto": {
"type": "object", "type": "object",
"properties": { "properties": {
"links": { "links": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/components/schemas/CustomerLink" "type": "string"
} }
} }
}, },
"required": [ "required": [
"links" "links"
] ]
},
"CustomerThemeRequest": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"backgroundColor": {
"type": "string"
},
"textColor": {
"type": "string"
}
},
"required": [
"displayName",
"backgroundColor",
"textColor"
]
},
"CustomerTheme": {
"type": "object",
"properties": {
"backgroundColor": {
"type": "string"
},
"textColor": {
"type": "string"
},
"logoWhite": {
"type": "string"
},
"logoBlack": {
"type": "string"
}
},
"required": [
"backgroundColor",
"textColor",
"logoWhite",
"logoBlack"
]
},
"CustomerThemeResponse": {
"type": "object",
"properties": {
"theme": {
"$ref": "#/components/schemas/CustomerTheme"
}
},
"required": [
"theme"
]
} }
} }
} }
-2
View File
@@ -12,8 +12,6 @@ declare global {
INTERNAL_SWAGGER: 'true' | 'false'; INTERNAL_SWAGGER: 'true' | 'false';
AWS_REGION: string; AWS_REGION: string;
OPEN_GROUP_ID: string;
OPEN_CUSTOMER_ID: string;
} }
} }
} }
-20
View File
@@ -1,20 +0,0 @@
charts:
- name: maestro
chart: ../maestro
values:
- ../maestro/values.yaml
set:
- name: maestro.duc_url
value: duc.dadosfera.ai
- name: hostname
value: maestro.dadosfera.ai
- name: maestro.pi_factory_url
value: pi-factory.dadosfera.ai
- name: maestro.in_factory_url
value: in-factory.dadosfera.ai
- name: maestro.tr_factory_url
value: in-factory.dadosfera.ai
- name: maestro.open_customer_id
value: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e
- name: maestro.open_group_id
value: 401573bb-334f-44b2-b30e-88d4cea31ae9
-22
View File
@@ -1,22 +0,0 @@
charts:
- name: maestro
chart: ../maestro
values:
- ../maestro/values.yaml
set:
- name: maestro.duc_url
value: duc.stg.dadosfera.ai
- name: hostname
value: maestro.stg.dadosfera.ai
- name: maestro.pi_factory_url
value: pi-factory.dadosfera.ai
- name: maestro.in_factory_url
value: in-factory.dadosfera.ai
- name: maestro.tr_factory_url
value: in-factory.dadosfera.ai
- name: maestro.open_customer_id
value: b3e3dfe5-b992-4586-a73c-c0b0c00f615d
- name: maestro.open_group_id
value: e3f98a2f-7748-4981-8505-7695c8ca8218
- name: replicaCount
value: 1
-23
View File
@@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
-24
View File
@@ -1,24 +0,0 @@
apiVersion: v2
name: maestro
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
View File
-117
View File
@@ -1,117 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: maestro
namespace: applications
labels:
app: maestro
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: maestro
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: maestro
spec:
imagePullSecrets:
- name: {{ .Values.imagePullSecrets }}
nodeSelector:
"beta.kubernetes.io/os": linux
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: application
operator: In
values:
- backend
tolerations:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
containers:
- name: maestro
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: {{ .Values.containerPort }}
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
env:
- name: AWS_IDENTITY_POOL_ID
value: {{ .Values.maestro.aws_identity_pool_id }}
- name: AWS_REGION
value: "us-east-1"
- name: BASE_HOST
value: "maestro_prd"
- name: BUCKET_CUSTOMER_CSV_ASSETS
value: {{ .Values.maestro.bucket_customer_csv_assets }}
- name: CONNECTORS_INDEX
value: "connectors"
- name: DUC_URL
value: {{ .Values.maestro.duc_url }}
- name: ELASTIC_APM_ENVIRONMENT
value: {{ .Values.maestro.env }}
- name: ELASTIC_APM_SERVER_URL
value: "https://apm-server.dadosfera.ai"
- name: ELASTIC_APM_SERVICE_NAME
value: {{ .Values.maestro.apm_service }}
- name: ENV
value: {{ .Values.maestro.env }}
- name: INFACTORY_URL
value: {{ .Values.maestro.in_factory_url }}
- name: LOGGER_GELF_HOST
value: "logstash-pipelines.dadosfera.ai"
- name: LOGGER_GELF_PORT
value: "{{ .Values.maestro.logger_gelf_port }}"
- name: NIMBUS_BASE_URL
value: "http://nimbus-api"
- name: NPM_TOKEN
value: {{ .Values.maestro.npm_token }}
- name: PB_TOKEN_PATH
value: {{ .Values.maestro.pb_token_path }}
- name: PIFACTORY_URL
value: {{ .Values.maestro.pi_factory_url }}
- name: SM_OAUTH_PATH
value: {{ .Values.maestro.sm_oauth_path }}
- name: TRFACTORY_URL
value: {{ .Values.maestro.tr_factory_url }}
- name: UPLOAD_FILE_AGENT_CONNECTION
value: {{ .Values.maestro.upload_file_agent_connection }}
- name: JWT_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: prd-duc
key: jwt_token
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: prd-maestro
key: AWS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: prd-maestro
key: AWS_SECRET_ACCESS_KEY
- name: AWS_DEFAULT_REGION
valueFrom:
secretKeyRef:
name: prd-maestro
key: AWS_DEFAULT_REGION
-27
View File
@@ -1,27 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/server-snippet: |
underscores_in_headers on;
ignore_invalid_headers on;
generation: 1
labels:
app: maestro
name: maestro
namespace: applications
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.hostname }}
http:
paths:
- backend:
service:
name: maestro
port:
number: {{ .Values.ingress.port }}
path: /
pathType: Prefix
-40
View File
@@ -1,40 +0,0 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: prd-maestro
namespace: applications
labels:
app: maestro
spec:
refreshInterval: 1h
secretStoreRef:
name: secretsmanager-prd
kind: SecretStore
target:
name: prd-maestro
creationPolicy: Owner
data:
- secretKey: AWS_ACCESS_KEY_ID
remoteRef:
key: prd/microservices/aws_credentials/maestro
version: "AWSCURRENT"
property: AWS_ACCESS_KEY_ID
- secretKey: AWS_SECRET_ACCESS_KEY
remoteRef:
key: prd/microservices/aws_credentials/maestro
version: "AWSCURRENT"
property: AWS_SECRET_ACCESS_KEY
- secretKey: AWS_DEFAULT_REGION
remoteRef:
key: prd/microservices/aws_credentials/maestro
version: "AWSCURRENT"
property: AWS_DEFAULT_REGION
- secretKey: jwt_token
remoteRef:
key: {{ .Values.maestro.jwt_token_secret_id }}
version: "AWSCURRENT"
property: token
-18
View File
@@ -1,18 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: maestro
namespace: applications
labels:
app: maestro
spec:
type: ClusterIP
ports:
- name: maestro
protocol: TCP
port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
selector:
app: maestro
-50
View File
@@ -1,50 +0,0 @@
# Default values for metabase.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 3
hostname: maestro-temp.dadosfera.ai
image:
repository: 611330257153.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_prd
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: 1.56.0
containerPort: 3333
imagePullSecrets: "applications-secrets-ecr-auth-token-external-secret"
service:
type: ClusterIP
port: 3333
targetPort: 3333
ingress:
enabled: false
port: 3333
resources:
requests:
cpu: 100m
memory: 1500Mi
limits:
cpu: 2000m
memory: 2Gi
maestro:
aws_identity_pool_id: "us-east-1_Mrezsw9Sn"
duc_url: duc.dadosfera.ai
in_factory_url: in-factory.dadosfera.ai
bucket_customer_csv_assets: "customers-csv-assets-prd-611330257153"
env: prd
apm_service: maestro
jwt_token_secret_id: prd/root/jwt_token
logger_gelf_port: "1026"
npm_token: npm_Xp17h3daMkORcZ55NY95Ez4gRfdzsQ3UvINP
pb_token_path: prd/root/productboard_token
pi_factory_url: pi-factory.dadosfera.ai
sm_oauth_path: prd/root/oauth_applications
tr_factory_url: in-factory.dadosfera.ai
upload_file_agent_connection: cbc2f881-58c4-4d60-8003-0979b0b5b911
open_customer_id: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e
open_group_id: 401573bb-334f-44b2-b30e-88d4cea31ae9
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
+11509 -4349
View File
File diff suppressed because it is too large Load Diff
+43 -43
View File
@@ -6,10 +6,10 @@
"private": true, "private": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"engines": { "engines": {
"node": "18.17" "node": "18.3.0"
}, },
"scripts": { "scripts": {
"co:login": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1", "preinstall": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1",
"proto-update": "npm i @dadosfera/protospack-v2@latest --save-exact", "proto-update": "npm i @dadosfera/protospack-v2@latest --save-exact",
"prebuild": "rimraf dist", "prebuild": "rimraf dist",
"build": "nest build", "build": "nest build",
@@ -20,80 +20,80 @@
"start:prod": "node dist/main", "start:prod": "node dist/main",
"docs": "export KILL_AFTER_START=1 && nest start && export INTERNAL_SWAGGER=true && nest start", "docs": "export KILL_AFTER_START=1 && nest start && export INTERNAL_SWAGGER=true && nest start",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest --detectOpenHandles --forceExit", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack": "2.5.3", "@dadosfera/protospack-v2": "3.31.0",
"@dadosfera/protospack-v2": "3.37.0-beta.3", "@grpc/grpc-js": "^1.6.7",
"@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.6.13",
"@grpc/proto-loader": "^0.7.9", "@nestjs/common": "^8.4.7",
"@nestjs/cli": "^9.5.0", "@nestjs/config": "^1.2.1",
"@nestjs/common": "^9.4.3", "@nestjs/core": "^8.4.7",
"@nestjs/config": "^2.3.4", "@nestjs/mapped-types": "*",
"@nestjs/core": "^9.4.3", "@nestjs/microservices": "^8.4.7",
"@nestjs/mapped-types": "^1.2.2", "@nestjs/passport": "^8.2.2",
"@nestjs/microservices": "^9.4.3", "@nestjs/platform-express": "^8.4.7",
"@nestjs/passport": "^9.0.3", "@nestjs/schedule": "^1.1.0",
"@nestjs/platform-express": "^9.4.3", "@nestjs/swagger": "^5.2.1",
"@nestjs/schematics": "^9.2.0",
"@nestjs/swagger": "^6.3.0",
"@nestjs/testing": "^9.4.3",
"axios": "^0.27.2", "axios": "^0.27.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.13.2",
"cron-parser": "^4.9.0", "cron-parser": "^4.4.0",
"dotenv": "^14.3.2", "dotenv": "^14.3.2",
"elastic-apm-node": "^3.50.0", "elastic-apm-node": "^3.36.0",
"helmet": "^5.1.1", "helmet": "^5.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.0",
"jwk-to-pem": "^2.0.5", "jwk-to-pem": "^2.0.5",
"mixpanel": "^0.17.0", "mixpanel": "^0.17.0",
"ms": "^3.0.0-canary.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
"passport-forcedotcom": "^0.2.0", "passport-forcedotcom": "^0.2.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"passport-hubspot-oauth2": "^1.0.3", "passport-hubspot-oauth2": "^1.0.3",
"passport-mailchimp": "^1.1.0", "passport-mailchimp": "^1.1.0",
"protospack": "2.5.2",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.5.5", "rxjs": "^7.5.5",
"swagger-ui-express": "^4.6.3" "swagger-ui-express": "^4.4.0"
}, },
"overrides": { "overrides": {
"multer": "1.4.5-lts.1" "multer": "1.4.5-lts.1"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17", "@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/jest": "27.0.2",
"@types/jsonwebtoken": "^8.5.9", "@types/jsonwebtoken": "^8.5.8",
"@types/jwk-to-pem": "^2.0.1", "@types/jwk-to-pem": "^2.0.1",
"@types/multer": "^1.4.12", "@types/multer": "^1.4.7",
"@types/node": "^16.18.52", "@types/node": "^16.11.41",
"@types/passport-facebook": "^2.1.11", "@types/passport-facebook": "^2.1.11",
"@types/passport-google-oauth20": "^2.0.11", "@types/passport-google-oauth20": "^2.0.11",
"@types/passport-oauth2": "^1.4.12", "@types/passport-oauth2": "^1.4.11",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.62.0", "@typescript-eslint/parser": "^5.29.0",
"eslint": "^8.49.0", "eslint": "^8.18.0",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"nock": "^13.3.3", "nock": "^13.2.7",
"prettier": "^2.8.8", "prettier": "^2.7.1",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"supertest": "^6.3.3", "supertest": "^6.2.3",
"ts-jest": "^27.1.5", "ts-jest": "^27.1.5",
"ts-loader": "^9.4.4", "ts-loader": "^9.3.1",
"ts-node": "^10.9.1", "ts-node": "^10.8.1",
"tsconfig-paths": "^3.14.2", "tsconfig-paths": "^3.14.1",
"typescript": "^4.9.5" "typescript": "^4.6.3"
} }
} }
+2 -5
View File
@@ -26,10 +26,9 @@ import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
import { ProductboardModule } from './modules/productboard/productboard.module'; import { ProductboardModule } from './modules/productboard/productboard.module';
import { MixpanelModule } from './modules/mixpanel/mixpanel.module'; import { MixpanelModule } from './modules/mixpanel/mixpanel.module';
import { CustomersModule } from './modules/customers/customers.module'; import { CustomersModule } from './modules/customers/customers.module';
import { OpenDataModule } from './modules/open-data/open-data.module';
import { ThemeModule } from './modules/theme/theme.module';
@Module({ @Module({
controllers: [],
providers: [ providers: [
DadosferaLogger, DadosferaLogger,
{ {
@@ -59,10 +58,8 @@ import { ThemeModule } from './modules/theme/theme.module';
ProductboardModule, ProductboardModule,
MixpanelModule, MixpanelModule,
CustomersModule, CustomersModule,
OpenDataModule,
ThemeModule,
//Always leave HealthModule last, so it is on the bottom of swagger //Always leave HealthModule last, so it is on the bottom of swagger
HealthModule HealthModule,
], ],
}) })
export class AppModule {} export class AppModule {}
@@ -44,7 +44,7 @@ class NoClassAuthController {
@Get('has-all-permissions') @Get('has-all-permissions')
@RequireAllPermissions( @RequireAllPermissions(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN, PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE, PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
) )
async hasAllPermissions(@Body() body) { async hasAllPermissions(@Body() body) {
return { body }; return { body };
@@ -53,7 +53,7 @@ class NoClassAuthController {
@Get('has-some-permission') @Get('has-some-permission')
@RequireSomePermission( @RequireSomePermission(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN, PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE, PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
) )
async hasSomePermission(@Body() body) { async hasSomePermission(@Body() body) {
return { body }; return { body };
@@ -62,7 +62,7 @@ class NoClassAuthController {
@Controller('class-auth-condition') @Controller('class-auth-condition')
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok') @AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
@RequireSomePermission(PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE) @RequireSomePermission(PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE)
class ClassAuthConditionController { class ClassAuthConditionController {
@Get('body') @Get('body')
async getBody(@Body() body) { async getBody(@Body() body) {
@@ -444,13 +444,13 @@ describe('authentication.guard', () => {
NoClassAuthTest(tokenZ, ['zendesk']); NoClassAuthTest(tokenZ, ['zendesk']);
ClassAuthConditionTest(tokenZ, ['zendesk']); ClassAuthConditionTest(tokenZ, ['zendesk']);
const tokenM = CreateToken([PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE]); const tokenM = CreateToken([PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE]);
NoClassAuthTest(tokenM, ['metabase']); NoClassAuthTest(tokenM, ['metabase']);
ClassAuthConditionTest(tokenM, ['metabase']); ClassAuthConditionTest(tokenM, ['metabase']);
const tokenZM = CreateToken([ const tokenZM = CreateToken([
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN, PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE, PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
]); ]);
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']); NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']); ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
+13 -41
View File
@@ -55,18 +55,9 @@ export const PERMISSIONS_GROUPS = {
'es-es': '', 'es-es': '',
}, },
}, },
GENERATE_TOKEN: {
seqid: 46,
claim: 'customer:generate-token',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Gerar Token de Acesso',
'en-us': 'Generate Acess Token',
'es-es': 'Gerar Token de Acceso',
},
},
}, },
}, },
PIPELINE: { PIPELINE: {
title: { title: {
'pt-br': 'Coletar | Pipelines', 'pt-br': 'Coletar | Pipelines',
@@ -116,6 +107,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
CONNECTION: { CONNECTION: {
title: { title: {
'pt-br': 'Coletar | Fontes de dados', 'pt-br': 'Coletar | Fontes de dados',
@@ -155,6 +147,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
NETWORK_CONFIG: { NETWORK_CONFIG: {
title: { title: {
'pt-br': 'Coletar | Redes', 'pt-br': 'Coletar | Redes',
@@ -184,6 +177,7 @@ export const PERMISSIONS_GROUPS = {
// }, // },
}, },
}, },
OUTPUT: { OUTPUT: {
title: { title: {
'pt-br': 'Output', 'pt-br': 'Output',
@@ -233,6 +227,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
TRANSFORMATIONS: { TRANSFORMATIONS: {
title: { title: {
'pt-br': 'Micro-transformações', 'pt-br': 'Micro-transformações',
@@ -282,6 +277,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
CATALOG: { CATALOG: {
title: { title: {
'pt-br': 'Explorar | Catálogo', 'pt-br': 'Explorar | Catálogo',
@@ -362,6 +358,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
CONNECTORS: { CONNECTORS: {
title: { title: {
'pt-br': 'Conectores', 'pt-br': 'Conectores',
@@ -411,6 +408,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
SNOWFLAKE: { SNOWFLAKE: {
title: { title: {
'pt-br': 'Explorar | Consolidar', 'pt-br': 'Explorar | Consolidar',
@@ -430,6 +428,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
ZENDESK: { ZENDESK: {
title: { title: {
'pt-br': 'Zendesk', 'pt-br': 'Zendesk',
@@ -449,7 +448,8 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
DATAVIZ: {
ANALYZE: {
title: { title: {
'pt-br': 'Analisar | Visualização', 'pt-br': 'Analisar | Visualização',
'en-us': 'Analyze | Visualization', 'en-us': 'Analyze | Visualization',
@@ -466,15 +466,6 @@ export const PERMISSIONS_GROUPS = {
'es-es': 'Acceso Metabase', 'es-es': 'Acceso Metabase',
}, },
}, },
},
},
INTELLIGENCE: {
title: {
'pt-br': 'Analisar | Inteligência',
'en-us': 'Analyze | Intelligence',
'es-es': 'Analizar | Inteligencia',
},
permissions: {
INTELLIGENCE: { INTELLIGENCE: {
seqid: 31, seqid: 31,
claim: 'intelligence:open', claim: 'intelligence:open',
@@ -487,6 +478,7 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
MODULES: { MODULES: {
title: { title: {
'pt-br': 'Módulos da Dadosfera', 'pt-br': 'Módulos da Dadosfera',
@@ -583,25 +575,6 @@ export const PERMISSIONS_GROUPS = {
}, },
}, },
}, },
CUSTOMER: {
title: {
'pt-br': 'Organização',
'en-us': 'Organization',
'es-es': 'Organización',
},
permissions: {
MONITORING_DASHBOARD: {
seqid: 47,
claim: 'customer:monitoring-dashboard',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Ver o dashboard de monitoramento',
'en-us': 'View the monitoring dashboard',
'es-es': 'Ver el dashboard de monitoreo',
},
},
},
},
}; };
export interface DadosferaModule { export interface DadosferaModule {
name: string; name: string;
@@ -614,8 +587,7 @@ export const DADOSFERA_MODULES: Array<DadosferaModule> = [
name: 'Intelligence Module', name: 'Intelligence Module',
description: 'Orchest Module', description: 'Orchest Module',
key: 'intelligence', key: 'intelligence',
permissionSeqId: permissionSeqId: PERMISSIONS_GROUPS.ANALYZE.permissions.INTELLIGENCE.seqid,
PERMISSIONS_GROUPS.INTELLIGENCE.permissions.INTELLIGENCE.seqid,
}, },
{ {
name: 'Proccessing Module', name: 'Proccessing Module',
-32
View File
@@ -1,32 +0,0 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SetMetadata } from '@nestjs/common';
export const SetOrigin = (origin: string) => SetMetadata('allowedOrigin', origin);
@Injectable()
export class CORSGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const response = context.switchToHttp().getResponse();
const request = context.switchToHttp().getRequest();
const origin = request.headers.origin;
// Obter a origem permitida através do decorador
const allowedOrigin = this.reflector.get<string>('allowedOrigin', context.getHandler());
if (process.env.ENV === "local") {
return true;
}
// Verifica se a origem da requisição é permitida
if (allowedOrigin && origin !== allowedOrigin) {
throw new ForbiddenException('Acesso não permitido pela política CORS');
}
response.setHeader('Access-Control-Allow-Origin', allowedOrigin);
return true;
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ describe('user.decorator', () => {
const fakeUserPayload = { const fakeUserPayload = {
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125', user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
username: 'super.admin', username: 'super.admin',
permissions: [PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE].map( permissions: [PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE].map(
({ seqid }) => seqid, ({ seqid }) => seqid,
), ),
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11', customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
+1 -3
View File
@@ -164,8 +164,6 @@ export class AuthClientService implements OnModuleInit {
async oauthSignIn(data: { username: string; token: string }) { async oauthSignIn(data: { username: string; token: string }) {
const { username, token } = data; const { username, token } = data;
return lastValueFrom( return lastValueFrom(this.authService.AuthOauthSignIn({ token, username }));
this.authService.AuthOauthSignIn({ token, username, refreshToken: '' }),
);
} }
} }
+1 -3
View File
@@ -6,9 +6,7 @@ import {
import { credentials } from '@grpc/grpc-js'; import { credentials } from '@grpc/grpc-js';
import { Catalog } from '@dadosfera/protospack-v2'; import { Catalog } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
process.env.PIFACTORY_URL.includes('0.0.0.0');
export class CatalogClientConfiguration { export class CatalogClientConfiguration {
public name = 'CatalogClientConfiguration'; public name = 'CatalogClientConfiguration';
-110
View File
@@ -31,7 +31,6 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { RequestUser, User } from 'src/decorators/user.decorator'; import { RequestUser, User } from 'src/decorators/user.decorator';
import { import {
BatchRemoveRlsRulesRequest,
GetDatasetCatalogTaskRes, GetDatasetCatalogTaskRes,
ICatalogAllRequest, ICatalogAllRequest,
ICatalogAllResponse, ICatalogAllResponse,
@@ -50,11 +49,6 @@ import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filt
import { Language } from 'src/decorators/language.decorator'; import { Language } from 'src/decorators/language.decorator';
import { LanguageEnum } from 'src/utils/languages.enum'; import { LanguageEnum } from 'src/utils/languages.enum';
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
import {
AddRlsRuleRequest,
GetNimbusDashboardsRequest,
GetRlsRulesRequest,
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
@ApiTags('Catalog') @ApiTags('Catalog')
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }]) @ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
@@ -639,108 +633,4 @@ export class CatalogController {
return response; return response;
} }
@Post('rls-rule')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async addRlsRule(@User() user: RequestUser, @Body() body: AddRlsRuleRequest) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
const response = await this.catalogService.addRlsRule(body, metadata);
return response;
}
@Get('rls-rule/:id')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async getOneRlsRule(@Param('id') id: string, @User() user: RequestUser) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
const idInt = parseInt(id);
const response = await this.catalogService.getOneRlsRule(idInt, metadata);
return response;
}
@Get('rls-rule')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async getRlsRules(
@User() user: RequestUser,
@Query() query: GetRlsRulesRequest,
) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
const response = await this.catalogService.getRlsRules(query, metadata);
return response;
}
@Delete('rls-rule/:id')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async removeRlsRule(@Param('id') id: string, @User() user: RequestUser) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
const idInt = parseInt(id);
const response = await this.catalogService.removeRlsRule(idInt, metadata);
return response;
}
@Delete('rls-rule')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async batchRemoveRlsRules(
@Query() query: BatchRemoveRlsRulesRequest,
@User() user: RequestUser,
) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
await this.catalogService.batchRemoveRlsRule(query, metadata);
return { message: 'OK' };
}
@Get('nimbus-dashboards')
@RequireAllPermissions(PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER)
async getNimbusDashboards(
@User() user: RequestUser,
@Body() body: GetNimbusDashboardsRequest,
) {
const { customer_id, customer_name, user_id, username } = user;
const metadata = PackTheMetadata({
customer_id,
customer_name,
user_id,
username,
});
const dashboards = await this.catalogService.getNimbusDashboards(
body,
metadata,
);
return JSON.parse(dashboards);
}
} }
+1 -76
View File
@@ -6,7 +6,6 @@ import {
Messages, Messages,
} from '@dadosfera/protospack-v2/dist/lib/Catalog'; } from '@dadosfera/protospack-v2/dist/lib/Catalog';
import { import {
BadRequestException,
HttpException, HttpException,
HttpStatus, HttpStatus,
Inject, Inject,
@@ -19,16 +18,7 @@ import { CatalogClientConfiguration } from './catalog-client';
import { UsersService } from '../users/users.service'; import { UsersService } from '../users/users.service';
import { RolesService } from '../roles/roles.service'; import { RolesService } from '../roles/roles.service';
import { Metadata } from '@grpc/grpc-js'; import { Metadata } from '@grpc/grpc-js';
import { import { IUpdateDataRequest, TriggerCatalogReq } from './dtos';
BatchRemoveRlsRulesRequest,
IUpdateDataRequest,
TriggerCatalogReq,
} from './dtos';
import {
AddRlsRuleRequest,
GetNimbusDashboardsRequest,
GetRlsRulesRequest,
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
class CatalogService implements OnModuleInit { class CatalogService implements OnModuleInit {
catalogReadService: ReadService.CatalogReadServices; catalogReadService: ReadService.CatalogReadServices;
@@ -343,7 +333,6 @@ class CatalogService implements OnModuleInit {
} as typeof data_asset; } as typeof data_asset;
}); });
} }
async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) { async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) {
const { session } = await lastValueFrom( const { session } = await lastValueFrom(
this.catalogWriteService.TriggerDatasetCataloging(data, metadata), this.catalogWriteService.TriggerDatasetCataloging(data, metadata),
@@ -356,70 +345,6 @@ class CatalogService implements OnModuleInit {
); );
return res; return res;
} }
async addRlsRule(data: AddRlsRuleRequest, metadata: Metadata) {
const res = await lastValueFrom(
this.catalogWriteService.AddRlsRule(data, metadata),
);
return res;
}
async removeRlsRule(id: number, metadata: Metadata) {
const res = await lastValueFrom(
this.catalogWriteService.RemoveRlsRule({ id }, metadata),
);
return res;
}
async batchRemoveRlsRule(
query: BatchRemoveRlsRulesRequest,
metadata: Metadata,
) {
const { id_rls, nimbus_dashboard_id } = query;
if (id_rls && nimbus_dashboard_id) {
throw new BadRequestException(
"You can't delete using both parameters. Choose either 'id_rls' or 'nimbus_dashboard_id'",
);
}
if (id_rls) {
await lastValueFrom(
this.catalogWriteService.RemoveRlsRulesByRlsId({ id_rls }, metadata),
);
} else if (nimbus_dashboard_id) {
await lastValueFrom(
this.catalogWriteService.RemoveRlsRulesByDashboardId(
{ nimbus_dashboard_id: parseInt(nimbus_dashboard_id) },
metadata,
),
);
}
return 'OK';
}
async getRlsRules(data: GetRlsRulesRequest, metadata: Metadata) {
const res = await lastValueFrom(
this.catalogReadService.GetRlsRules(data, metadata),
);
return res.rls_rules;
}
async getOneRlsRule(id: number, metadata: Metadata) {
const res = await lastValueFrom(
this.catalogReadService.GetOneRlsRule({ id }, metadata),
);
return res.rls_rule;
}
async getNimbusDashboards(
data: GetNimbusDashboardsRequest,
metadata: Metadata,
) {
const res = await lastValueFrom(
this.catalogReadService.GetNimbusDashboards(data, metadata),
);
return res.dashboards;
}
} }
export { CatalogService }; export { CatalogService };
-8
View File
@@ -312,11 +312,3 @@ export class GetDatasetCatalogTaskRes {
@ApiProperty() @ApiProperty()
updated_by: string; updated_by: string;
} }
export class BatchRemoveRlsRulesRequest {
@ApiPropertyOptional()
nimbus_dashboard_id?: string;
@ApiPropertyOptional()
id_rls?: string;
}
@@ -6,9 +6,7 @@ import {
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { ConnectionTest } from '@dadosfera/protospack-v2'; import { ConnectionTest } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class ConnectionTestClientConfiguration { export class ConnectionTestClientConfiguration {
private config: GrpcOptions = { private config: GrpcOptions = {
+1 -3
View File
@@ -6,9 +6,7 @@ import {
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { ConnectionManager } from '@dadosfera/protospack-v2'; import { ConnectionManager } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class ConnectionClientConfiguration { export class ConnectionClientConfiguration {
public name = 'ConnectionClientConfiguration'; public name = 'ConnectionClientConfiguration';
@@ -24,7 +24,6 @@ import {
ConnectionDetailsRes, ConnectionDetailsRes,
ConnectionRes, ConnectionRes,
ConnectionsRes, ConnectionsRes,
GetAllConnectionsReq,
UpdateConnectionDto, UpdateConnectionDto,
} from './dtos/connection'; } from './dtos/connection';
import { CreateConnectionDto } from './dtos/connection'; import { CreateConnectionDto } from './dtos/connection';
@@ -139,7 +138,7 @@ export class ConnectionController {
async getAllConnections( async getAllConnections(
@User() user: RequestUser, @User() user: RequestUser,
@Language() language: LanguageEnum, @Language() language: LanguageEnum,
@Query() queries: GetAllConnectionsReq, @Query() queries,
): Promise<ConnectionsRes> { ): Promise<ConnectionsRes> {
this.logger.info('/connections - Get All Connection'); this.logger.info('/connections - Get All Connection');
+2 -14
View File
@@ -9,7 +9,6 @@ import {
ConnectionToCatalog, ConnectionToCatalog,
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities'; } from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities';
import { import {
GetAllConnectionRequest,
GetAllConnectionResponse, GetAllConnectionResponse,
GetConnectionDetailsResponse, GetConnectionDetailsResponse,
GetConnectionResponse, GetConnectionResponse,
@@ -22,7 +21,7 @@ export type ConnectionCredentialsType =
| 'oauth' | 'oauth'
| 'api_key' | 'api_key'
| 'service_account' | 'service_account'
| 'headers_auth'; | 'headers_authF';
export interface ConnectionApiConnection { export interface ConnectionApiConnection {
config_id: string; config_id: string;
plugin: string; plugin: string;
@@ -95,6 +94,7 @@ export class ConnectionDto implements Connection {
export class ConnectionToCatalogDto implements ConnectionToCatalog { export class ConnectionToCatalogDto implements ConnectionToCatalog {
// ---Automatically generated information - will not be sent by the frontend--- // // ---Automatically generated information - will not be sent by the frontend--- //
id: string;
customer_id: string; customer_id: string;
updated_at: string; updated_at: string;
created_at: string; created_at: string;
@@ -104,9 +104,6 @@ export class ConnectionToCatalogDto implements ConnectionToCatalog {
customer_name: string; customer_name: string;
// ---Information sent by the frontend--- // // ---Information sent by the frontend--- //
@ApiPropertyOptional()
id: string;
@ApiProperty() @ApiProperty()
name: string; name: string;
@@ -150,15 +147,6 @@ export class UpdateConnectionDto extends PickType(ConnectionDto, [
'properties', 'properties',
]) {} ]) {}
export class GetAllConnectionsReq implements GetAllConnectionRequest {
@ApiPropertyOptional()
search?: string;
@ApiPropertyOptional()
page?: string;
@ApiPropertyOptional()
size?: string;
}
export class ConnectionsRes implements GetAllConnectionResponse { export class ConnectionsRes implements GetAllConnectionResponse {
@ApiProperty({ type: [ConnectionToCatalogDto] }) @ApiProperty({ type: [ConnectionToCatalogDto] })
connections: ConnectionToCatalogDto[]; connections: ConnectionToCatalogDto[];
+1 -3
View File
@@ -6,9 +6,7 @@ import {
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { ConnectorManager } from '@dadosfera/protospack-v2'; import { ConnectorManager } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class ConnectorClientConfiguration { export class ConnectorClientConfiguration {
public name = 'ConnectorClientConfiguration'; public name = 'ConnectorClientConfiguration';
+13 -50
View File
@@ -1,31 +1,29 @@
import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { IdResponse } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { import {
Body, Body,
Controller, Controller,
Get, Get,
HttpCode,
HttpStatus,
Inject, Inject,
Param, Param,
Put, Put,
Query,
UseFilters, UseFilters,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiOkResponse, ApiProduces, ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import { import {
Authenticated, Authenticated,
RequireAllPermissions, RequireAllPermissions,
} from 'src/decorators/authentication.decorator'; } from 'src/decorators/authentication.decorator';
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
import { CustomersService } from './customers.service'; import { CustomersService } from './customers.service';
import { CustomerLinkRequest, CustomerLinksResponse } from './dtos/customers'; import { CustomerDto, CustomerLinksResponse } from './dtos/customers';
import { RequestUser, User } from 'src/decorators/user.decorator';
import type { StringValue } from 'ms';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
@ApiInternalOnlyController()
@ApiTags('Customers') @ApiTags('Customers')
@Controller('customers') @Controller('customers')
@Authenticated()
@UseFilters(GrpcToHttpExceptionFilter) @UseFilters(GrpcToHttpExceptionFilter)
export class CustomersController { export class CustomersController {
logger: DadosferaLogger; logger: DadosferaLogger;
@@ -39,56 +37,21 @@ export class CustomersController {
} }
@Get(':id/links') @Get(':id/links')
@Authenticated() @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@ApiOkResponse({ type: CustomerLinksResponse }) async getCustomerLinks(@Param('id') id): Promise<CustomerLinksResponse> {
async getCustomerLinks(@Param('id') id: string) {
this.logger.info('getCustomerLinks', { id }); this.logger.info('getCustomerLinks', { id });
const links = await this.customersService.getLinks(id); const links = await this.customersService.getLinks(id);
return { links }; return { links };
} }
@Put(':id/links') @Put(':id/links')
@Authenticated()
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@ApiOkResponse() setCustomerLinks(
@HttpCode(HttpStatus.OK) @Body() body: CustomerDto,
async setCustomerLinks( @Param('id') id,
@Body() body: CustomerLinkRequest, ): Promise<IdResponse> {
@Param('id') id: string,
) {
const { links } = body; const { links } = body;
this.logger.info('setCustomerLinks', { id, links }); this.logger.info('setCustomerLinks', { id, links });
await this.customersService.setLinks(id, links); return this.customersService.setLinks(id, links);
}
@Get('token')
@Authenticated()
@RequireAllPermissions(PERMISSIONS_GROUPS.AUTH.permissions.GENERATE_TOKEN)
@ApiProduces('text/plain')
async getCustomerToken(
@Query('exp') exp: StringValue,
@User() user: RequestUser,
): Promise<string> {
this.logger.info('getCustomerToken', { exp, user });
const data = {
customerId: user.customer_id,
userId: user.user_id,
customerName: user.customer_name,
};
return this.customersService.generateToken(exp, data);
}
@Get('monitoring-dashboard')
@Authenticated()
@RequireAllPermissions(
PERMISSIONS_GROUPS.CUSTOMER.permissions.MONITORING_DASHBOARD,
)
async getCustomerMonitoringDashboard(
@User() user: RequestUser,
): Promise<{ url: string }> {
this.logger.info('getCustomerMonitoringDashboard');
const metadata = PackTheMetadata(user);
return this.customersService.getMonitoringDashboardUrl(metadata);
} }
} }
+2 -9
View File
@@ -4,18 +4,11 @@ import { ClientsModule } from '@nestjs/microservices';
import { DucClient } from '../duc/client.config'; import { DucClient } from '../duc/client.config';
import { CustomersController } from './customers.controller'; import { CustomersController } from './customers.controller';
import { CustomersService } from './customers.service'; import { CustomersService } from './customers.service';
import { PipelinesClientConfiguration } from '../pipelinesV2/pipelines-client';
const ducClient = new DucClient(); const client = new DucClient();
const piFactoryClient = new PipelinesClientConfiguration();
@Module({ @Module({
imports: [ imports: [ClientsModule.register([client.providerOptions])],
ClientsModule.register([
ducClient.providerOptions,
piFactoryClient.providerOptions,
]),
],
controllers: [CustomersController], controllers: [CustomersController],
providers: [CustomersService, DadosferaLogger], providers: [CustomersService, DadosferaLogger],
exports: [CustomersService], exports: [CustomersService],
+1 -97
View File
@@ -4,7 +4,6 @@ import {
Injectable, Injectable,
HttpException, HttpException,
HttpStatus, HttpStatus,
InternalServerErrorException,
} from '@nestjs/common'; } from '@nestjs/common';
import { firstValueFrom, lastValueFrom } from 'rxjs'; import { firstValueFrom, lastValueFrom } from 'rxjs';
@@ -15,46 +14,19 @@ import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { CustomersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { CustomersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import ErrorCodes from 'src/utils/errorCodes'; import ErrorCodes from 'src/utils/errorCodes';
import jwt from 'jsonwebtoken';
import {
GetSecretValueCommand,
SecretsManagerClient,
} from '@aws-sdk/client-secrets-manager';
import getEnv from 'src/utils/getEnv';
import { logger } from 'elastic-apm-node';
import {
ReadService,
ProtoServices as PipelineProtoServices,
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
import { Metadata } from '@grpc/grpc-js';
import { PipelinesClientConfiguration } from '../pipelinesV2/pipelines-client';
import DadosferaLogger from '@dadosfera/dadosfera-logs';
// This function will accept any string, which may result in a bug.
@Injectable() @Injectable()
export class CustomersService implements OnModuleInit { export class CustomersService implements OnModuleInit {
private customerService: CustomersProtoService; private customerService: CustomersProtoService;
private logger: DadosferaLogger;
private pipelineReadService: ReadService.PipelineV2ReadService;
constructor( constructor(
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc, @Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
@Inject(PipelinesClientConfiguration.name) ) {}
private readonly pipelinesGrpcClient: ClientGrpc,
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
onModuleInit() { onModuleInit() {
this.customerService = this.grpcClient.getService<CustomersProtoService>( this.customerService = this.grpcClient.getService<CustomersProtoService>(
ProtoServices.CustomersProtoService, ProtoServices.CustomersProtoService,
); );
this.pipelineReadService =
this.pipelinesGrpcClient.getService<ReadService.PipelineV2ReadService>(
PipelineProtoServices.PipelineV2ReadService,
);
} }
async getLinks(customerId: string) { async getLinks(customerId: string) {
@@ -88,72 +60,4 @@ export class CustomersService implements OnModuleInit {
else throw err; else throw err;
} }
} }
async generateToken(
expiresIn = '30m',
data: { customerId: string; userId: string; customerName: string },
) {
const { customerId, userId: generatedById, customerName } = data;
const secretsManagerClient = new SecretsManagerClient({});
const getSecretComand = new GetSecretValueCommand({
SecretId: `${getEnv()}/${customerName}/jwt-signing-key`,
});
let private_key_pem;
await secretsManagerClient
.send(getSecretComand)
.then((res) => {
const secret = JSON.parse(res.SecretString);
private_key_pem = secret.private_key_pem;
})
.catch((err) => {
logger.error(err.stack);
throw new InternalServerErrorException(
'Error finding keys for customer',
);
});
let jwt_token;
try {
jwt_token = jwt.sign(
{
customerId,
generatedById,
},
private_key_pem,
{
algorithm: 'RS256',
expiresIn,
issuer: 'maestro',
},
);
} catch (err) {
logger.error(err.stack);
throw new InternalServerErrorException('Error generating token');
}
// const verify = jwt.verify(jwt_token, public_key_pem, {
// algorithms: ['RS256'],
// issuer: 'maestro',
// });
// const decoded = jwt.decode(jwt_token, { complete: true });
return jwt_token;
}
async getMonitoringDashboardUrl(metadata: Metadata) {
logger.info('CustomersService - getMonitoringDashboardUrl');
const res = await lastValueFrom(
this.pipelineReadService.PipelineV2GetDashboardUrl(
{
dashboard_id: '45',
exp: '15m',
metabase_customer_name: 'dadosferatech',
},
metadata,
),
);
logger.info('Done');
return res;
}
} }
+5 -16
View File
@@ -1,23 +1,12 @@
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
export class CustomerLink implements Link { export class CustomerDto {
@ApiProperty() @ApiProperty()
href: string; links: Link[];
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiPropertyOptional()
iconSrc: string;
}
export class CustomerLinkRequest {
@ApiProperty({ type: [CustomerLink] })
links: CustomerLink[];
} }
export class CustomerLinksResponse { export class CustomerLinksResponse {
@ApiProperty({ type: [CustomerLink] }) @ApiProperty()
links: CustomerLink[]; links: Link[];
} }
+1 -5
View File
@@ -9,9 +9,7 @@ import {
ProtoPaths, ProtoPaths,
} from '@dadosfera/protospack-v2/dist/lib/Duc'; } from '@dadosfera/protospack-v2/dist/lib/Duc';
const isLocalConnection = const isLocalConnection = !!process.env.DUC_URL?.includes('0.0.0.0');
process.env.DUC_URL.startsWith('duc:') ||
process.env.DUC_URL.includes('0.0.0.0');
export class DucClient { export class DucClient {
public name = 'DucClient'; public name = 'DucClient';
@@ -29,8 +27,6 @@ export class DucClient {
objects: true, objects: true,
arrays: true, arrays: true,
}, },
maxSendMessageLength: 15 * 1024 * 1024, // 15 MB por mensagem
maxReceiveMessageLength: 15 * 1024 * 1024,
}, },
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
import { Info } from '@dadosfera/protospack/dist/lib/interfaces'; import { Info } from 'protospack/dist/lib/interfaces';
interface Values { interface Values {
jdbc_user: string; jdbc_user: string;
+1 -3
View File
@@ -6,9 +6,7 @@ import {
type GrpcOptions, type GrpcOptions,
} from '@nestjs/microservices'; } from '@nestjs/microservices';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class InputsGrpcClient { export class InputsGrpcClient {
public readonly name = 'InputsGrpcClient'; public readonly name = 'InputsGrpcClient';
private config: GrpcOptions = { private config: GrpcOptions = {
+2
View File
@@ -5,6 +5,7 @@ import {
Inject, Inject,
Injectable, Injectable,
} from '@nestjs/common'; } from '@nestjs/common';
import { Timeout } from '@nestjs/schedule';
import CronParser, { CronExpression } from 'cron-parser'; import CronParser, { CronExpression } from 'cron-parser';
import { ClientGrpc } from '@nestjs/microservices'; import { ClientGrpc } from '@nestjs/microservices';
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist'; import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
@@ -221,6 +222,7 @@ export class InputsService {
return lastValueFrom(this.inputWriteService.InputRemove(idRequest)); return lastValueFrom(this.inputWriteService.InputRemove(idRequest));
} }
@Timeout(60000 * 10) // Timeout set for 10 minutes
async testConnection(data) { async testConnection(data) {
try { try {
const testConnectionInputResponse = const testConnectionInputResponse =
@@ -6,9 +6,7 @@ import {
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { NetworkConfig } from '@dadosfera/protospack-v2'; import { NetworkConfig } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class NetworkConfigGrpcClient { export class NetworkConfigGrpcClient {
public name = 'NetworkConfigGrpcClient'; public name = 'NetworkConfigGrpcClient';
@@ -1,48 +0,0 @@
import { ApiProperty } from "@nestjs/swagger";
export class CreateUserOpenDataDTO {
@ApiProperty()
firstName: string;
@ApiProperty()
lastName: string;
@ApiProperty()
email: string;
@ApiProperty()
organization: string;
@ApiProperty()
enquiryType: string;
}
type FormField = {
id: string;
type: string;
title: string;
value: string;
raw_value: string;
required: string;
};
type MetaData = {
title: string;
value: string;
};
export type WordpressForm = {
form: {
id: string;
name: string;
};
fields: {
[key: string]: FormField;
};
meta: {
date: MetaData;
time: MetaData;
page_url: MetaData;
user_agent: MetaData;
remote_ip: MetaData;
credit: MetaData;
};
};
@@ -1,108 +0,0 @@
import DadosferaLogger from '@dadosfera/dadosfera-logs';
import { Body, Controller, Header, HttpCode, Inject, Param, Post, Query, Req, UseFilters, UseGuards } from '@nestjs/common';
import { ApiCreatedResponse, ApiHeaders, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
import { LanguageEnum } from 'src/utils/languages.enum';
import { UsersService } from '../users/users.service';
import { Language } from 'src/decorators/language.decorator';
import { OpenDataService } from './open-data.service';
import { CreateUserOpenDataDTO, WordpressForm } from './dto/wordpres-form';
import { CORSGuard, SetOrigin } from 'src/decorators/set-origin.decorator';
import { Metadata } from '@grpc/grpc-js';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { request } from 'http';
import { Request } from 'express';
@Controller('open-data')
@ApiInternalOnlyController()
@ApiTags('OpenData')
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
@UseFilters(GrpcToHttpExceptionFilter)
export class OpenDataController {
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private openDataService: OpenDataService,
) {
this.logger = dadosferaLogger.logger;
}
@Post("/sharing-ocean-data")
// @SetOrigin('devsbm.dadosfera.io')
// @UseGuards(CORSGuard)
@HttpCode(200)
@Header('content-type', 'application/json')
@ApiOkResponse()
async createUser(
@Body()
body: WordpressForm,
@Query('language')
language: string,
@Req()
request: Request,
) {
this.logger.info('createUser for open data'
+ JSON.stringify({
origin: request.headers.origin,
language,
body
}));
// "401573bb-334f-44b2-b30e-88d4cea31ae9"
// const OPENDATA_PUBLIC_USERS_GROUP_ID = process.env.OPEN_GROUP_ID;
// ""f239718a-a271-4ef9-ae7e-02a2f0f3aa6e""
// const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID;
const OPENDATA_CUSTOMER_ID = "b3e3dfe5-b992-4586-a73c-c0b0c00f615d";
this.logger.info("OPENDATA_CUSTOMER_ID: " + process.env.OPEN_CUSTOMER_ID)
this.logger.info("OPEN_GROUP_ID: " + process.env.OPEN_GROUP_ID)
const roles = ["e3f98a2f-7748-4981-8505-7695c8ca8218"];
const metadata = PackTheMetadata({
language: language || 'en-us'
});
const data = {}
try {
Object.keys(body.fields)
.filter(key => body.fields[key].required === "1")
.forEach(key => {
const field = body.fields[key]
data[field.id] = field.value
});
} catch (e) {
this.logger.error('user data ' + e.message);
}
const user: CreateUserOpenDataDTO = {
email: data["email"],
enquiryType: data["enquiry_type"],
firstName: data["first_name"],
lastName: data["last_name"],
organization: data["organization"]
}
this.logger.info('user request' + JSON.stringify({ user, roles, customer: OPENDATA_CUSTOMER_ID }));
try {
await this.openDataService.createUser(OPENDATA_CUSTOMER_ID, user, roles, metadata);
this.logger.info('user created with sucessfull data');
return {
success: true,
status: 'success',
message: 'user created with succesfull'
}
} catch (e) {
this.logger.error('failed with exception: ' + e.message);
return {
success: false,
status: 'failed',
message: e.message
};
}
}
}
-22
View File
@@ -1,22 +0,0 @@
import { Module } from '@nestjs/common';
import { OpenDataController } from './open-data.controller';
import { UsersService } from '../users/users.service';
import { ClientsModule } from '@nestjs/microservices'
import { DucClient } from '../duc/client.config';
import DadosferaLogger from '@dadosfera/dadosfera-logs';
import { RolesModule } from '../roles/roles.module';
import { PermissionsModule } from '../permissions/permissions.module';
import { OpenDataService } from './open-data.service';
const client = new DucClient();
@Module({
controllers: [OpenDataController],
imports: [
ClientsModule.register([client.providerOptions]),
RolesModule,
// PermissionsModule,
],
providers: [DadosferaLogger, UsersService, OpenDataService]
})
export class OpenDataModule {}
@@ -1,51 +0,0 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { lastValueFrom } from 'rxjs';
import { CreateUserOpenDataDTO } from './dto/wordpres-form';
import DadosferaLogger from '@dadosfera/dadosfera-logs';
import { UsersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import { DucClient } from '../duc/client.config';
import { ClientGrpc } from '@nestjs/microservices';
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { Metadata } from '@grpc/grpc-js';
@Injectable()
export class OpenDataService implements OnModuleInit {
logger: DadosferaLogger;
private usersClientService: UsersProtoService;
constructor(
@Inject(DadosferaLogger)
private dadosferaLogger: DadosferaLogger,
@Inject(DucClient.name)
private readonly grpcClient: ClientGrpc,
) {
this.logger = dadosferaLogger.logger;
}
onModuleInit() {
this.usersClientService = this.grpcClient.getService(
ProtoServices.UsersProtoService,
);
}
async createUser(customerId: string, data: CreateUserOpenDataDTO, roleIds: string[], metadata: Metadata) {
const body = {
email: data.email,
name: data.firstName + " " + data.lastName,
department: data.organization,
jobTitle: data.enquiryType,
customerId: customerId,
roleIds: roleIds
}
try {
await lastValueFrom(
this.usersClientService.SimpleUserCreate(body, metadata),
);
return "User created";
} catch(err) {
return err;
}
}
}
+2 -6
View File
@@ -1,9 +1,6 @@
import { ConflictException, Inject, OnModuleInit } from '@nestjs/common'; import { ConflictException, Inject, OnModuleInit } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices'; import { ClientGrpc } from '@nestjs/microservices';
import { import { PipelineServicesNames, PipelinesServiceInterface } from 'protospack';
PipelineServicesNames,
PipelinesServiceInterface,
} from '@dadosfera/protospack';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { IIdRequest } from './interfaces'; import { IIdRequest } from './interfaces';
@@ -14,10 +11,9 @@ import { PipelinesClientConfiguration } from './pipelines-client';
export class PipelinesClientService implements OnModuleInit { export class PipelinesClientService implements OnModuleInit {
private pipelineService: PipelinesServiceInterface; private pipelineService: PipelinesServiceInterface;
logger: DadosferaLogger; logger: DadosferaLogger;
constructor( constructor(
@Inject(DadosferaLogger) @Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger, dadosferaLogger: DadosferaLogger,
@Inject(PipelinesClientConfiguration.name) @Inject(PipelinesClientConfiguration.name)
private readonly grpcClient: ClientGrpc, private readonly grpcClient: ClientGrpc,
) { ) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Info } from '@dadosfera/protospack/dist/lib/interfaces'; import { Info } from 'protospack/dist/lib/interfaces';
export interface ICreatePipelineDto { export interface ICreatePipelineDto {
input: IdRequest; input: IdRequest;
+2 -4
View File
@@ -3,12 +3,10 @@ import {
GrpcOptions, GrpcOptions,
Transport, Transport,
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { PipelinePackages, PipelineProtoFilePath } from '@dadosfera/protospack'; import { PipelinePackages, PipelineProtoFilePath } from 'protospack';
import { credentials } from '@grpc/grpc-js'; import { credentials } from '@grpc/grpc-js';
const isLocalConnection = const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
process.env.PIFACTORY_URL.includes('0.0.0.0');
export class PipelinesClientConfiguration { export class PipelinesClientConfiguration {
public name = 'PipelinesClientConfiguration'; public name = 'PipelinesClientConfiguration';
+1 -1
View File
@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger'; import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
import { Info } from '@dadosfera/protospack/dist/lib/interfaces'; import { Info } from 'protospack/dist/lib/interfaces';
export class IPipelineV2 { export class IPipelineV2 {
@ApiProperty() @ApiProperty()
+1 -3
View File
@@ -9,9 +9,7 @@ import {
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2'; } from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
import { credentials } from '@grpc/grpc-js'; import { credentials } from '@grpc/grpc-js';
const isLocalConnection = const isLocalConnection = !!process.env.PIFACTORY_URL?.includes('0.0.0.0');
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
process.env.PIFACTORY_URL.includes('0.0.0.0');
export class PipelinesClientConfiguration { export class PipelinesClientConfiguration {
public name = 'PipelinesClientConfiguration'; public name = 'PipelinesClientConfiguration';
@@ -14,7 +14,6 @@ import {
Patch, Patch,
HttpException, HttpException,
BadRequestException, BadRequestException,
CacheTTL,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
ApiCreatedResponse, ApiCreatedResponse,
@@ -87,18 +86,6 @@ export class PipelinesController {
this.logger = dadosferaLogger.logger; this.logger = dadosferaLogger.logger;
} }
@Get('monitoring-dashboard')
async getMonitoringDashboard(@User() user: RequestUser) {
this.logger.info('PipelinesController - getMonitoringDashboard', { user });
const metadata = PackTheMetadata(user);
const response =
await this.pipelinesClientService.getMonitoringDashboardUrl(metadata);
return response;
}
@Post() @Post()
@ApiCreatedResponse({ type: IPipelineV2 }) @ApiCreatedResponse({ type: IPipelineV2 })
async create( async create(
@@ -321,22 +321,4 @@ export class PipelinesService implements OnModuleInit {
); );
}); });
} }
async getMonitoringDashboardUrl(metadata: Metadata) {
this.logger.info('PipelinesClientService - getMonitoringDashboardUrl');
const res = await lastValueFrom(
this.pipelineReadService.PipelineV2GetDashboardUrl(
{
dashboard_id: '83',
exp: '15m',
metabase_customer_name: 'dadosferatech',
},
metadata,
),
);
this.logger.info('Done');
return res;
}
} }
@@ -14,42 +14,44 @@ import { INote } from './dtos';
export class ProductboardController { export class ProductboardController {
@Post('notes') @Post('notes')
async sendNote(@Body() note: INote, @User() user: RequestUser) { async sendNote(@Body() note: INote, @User() user: RequestUser) {
const { title, content, tags } = note; const [request_type, urgency, connector_name = 'Não informado'] = note.tags;
const { username, customer_name } = user;
const email = username.includes('@') const { title, content } = note;
? username // const { username, customer_name } = user;
: username + `@${customer_name}.default`;
const path = process.env.PB_TOKEN_PATH; // const email = username.includes('@')
const token = await getSecretFromSecretsManager(path); // ? username
// : username + `@${customer_name}.default`;
const response = await axios // const path = process.env.PB_TOKEN_PATH;
.post( // const token = await getSecretFromSecretsManager(path);
`https://api.productboard.com/notes`,
{
title,
content,
tags,
user: {
email,
},
},
{
headers: {
Authorization: 'Bearer ' + token,
},
},
)
.catch(() => null);
if (!response) { // const response = await axios
throw new HttpException( // .post(
'Your request could not be submitted, please try again in a few moments or contact support.', // `https://api.productboard.com/notes`,
502, // {
); // title,
} // content,
// tags,
// user: {
// email,
// },
// },
// {
// headers: {
// Authorization: 'Bearer ' + token,
// },
// },
// )
// .catch(() => null);
return response.data; // if (!response) {
// throw new HttpException(
// 'Your request could not be submitted, please try again in a few moments or contact support.',
// 502,
// );
// }
// return response.data;
} }
} }
-47
View File
@@ -1,47 +0,0 @@
import { Link, Theme } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CustomerLink implements Link {
@ApiProperty()
href: string;
@ApiProperty()
name: string;
@ApiProperty()
description: string;
@ApiPropertyOptional()
iconSrc: string;
}
export class CustomerLinkRequest {
@ApiProperty({ type: [CustomerLink] })
links: CustomerLink[];
}
export class CustomerLinksResponse {
@ApiProperty({ type: [CustomerLink] })
links: CustomerLink[];
}
export class CustomerTheme implements Theme {
@ApiProperty()
backgroundColor: string;
@ApiProperty()
textColor: string;
@ApiProperty()
logoWhite: string;
@ApiProperty()
logoBlack: string;
}
export class CustomerThemeResponse {
@ApiProperty()
theme: CustomerTheme;
}
export class CustomerThemeRequest {
@ApiProperty()
displayName: string;
@ApiProperty()
backgroundColor: string;
@ApiProperty()
textColor: string;
}
-125
View File
@@ -1,125 +0,0 @@
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Inject,
Param,
Post,
Put,
Query,
UploadedFiles,
UseFilters,
UseInterceptors,
HttpCode
} from '@nestjs/common';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import {
Authenticated,
RequireAllPermissions,
} from 'src/decorators/authentication.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
import { CustomerThemeRequest, CustomerThemeResponse } from './dtos/customers';
import ErrorCodes from 'src/utils/errorCodes';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { ThemeService } from './theme.service';
@ApiTags('Theme')
@Controller('customers')
@UseFilters(GrpcToHttpExceptionFilter)
export class ThemeController {
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private themeService: ThemeService,
) {
this.logger = dadosferaLogger.logger;
}
@Post('/:id/theme')
@Authenticated()
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
@ApiOkResponse({ type: CustomerThemeResponse })
@UseInterceptors(AnyFilesInterceptor())
@HttpCode(HttpStatus.OK)
async saveCustomertheme(
@Param('id') id: string,
@Body() data: CustomerThemeRequest,
@UploadedFiles() files: Array<Express.Multer.File>
) {
this.logger.info('saveCustomertheme' + JSON.stringify({
id,
}));
const logoWhite = files.find(file => file.fieldname === 'logoWhite');
const logoBlack = files.find(file => file.fieldname === 'logoBlack');
this.validFileSize(logoWhite);
this.validFileSize(logoBlack);
this.validMimeType(logoWhite);
this.validMimeType(logoBlack);
try {
const theme = await this.themeService.createThemeByCustomer(id, {
...data,
logoWhite,
logoBlack
});
this.logger.info('saveCustomertheme' + JSON.stringify(theme));
return { theme: theme };
} catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) {
this.logger.error('Error - saveCustomertheme - Expect CUSTOMER.NOT_FOUND');
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
} else {
this.logger.error('Error - saveCustomertheme Unknown Error:' + err?.message);
return { theme: null };
};
}
}
private validFileSize(file: Express.Multer.File) {
const maxFileSize = 10 * 1024 * 1024; // 10MB
if (file && file.size > maxFileSize) {
throw new HttpException(`O Arquivo ${file.filename} possui mais de 10MB`, HttpStatus.BAD_REQUEST);
}
}
private validMimeType(file: Express.Multer.File) {
const mimeTypesValid = ['image/jpeg', 'image/jpg', 'image/png'];
if (file && !mimeTypesValid.includes(file.mimetype)) {
throw new HttpException(`O Arquivo ${file.fieldname} deve ser jpeg, jpg ou png`, HttpStatus.BAD_REQUEST);
}
}
@Get('/:id/theme')
@ApiOkResponse({ type: CustomerThemeResponse })
async getCustomerTheme(@Param('id') id: string) {
this.logger.info('getCustomerTheme with id' + id);
try {
const data = await this.themeService.getThemeByCustomer(id);
this.logger.info('Success - getCustomerTheme with id'+ id);
return data;
}catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) {
this.logger.error('Error - getCustomerTheme - Expect CUSTOMER.NOT_FOUND');
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
} else {
this.logger.error('Error - getCustomerTheme Unknown Error:' + err?.message);
return { theme: null };
};
}
}
}
-20
View File
@@ -1,20 +0,0 @@
import { Module } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { ClientsModule } from '@nestjs/microservices';
import { ThemeController } from './theme.controller';
import { ThemeService } from './theme.service';
import { DucClient } from '../duc/client.config';
const ducClient = new DucClient();
@Module({
imports: [
ClientsModule.register([
ducClient.providerOptions,
]),
],
controllers: [ThemeController],
providers: [ThemeService, DadosferaLogger],
exports: [ThemeService],
})
export class ThemeModule {}
-133
View File
@@ -1,133 +0,0 @@
import {
OnModuleInit,
Inject,
Injectable,
HttpException,
HttpStatus,
InternalServerErrorException,
} from '@nestjs/common';
import { firstValueFrom, lastValueFrom, ReplaySubject } from 'rxjs';
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import { DucClient } from '../duc/client.config';
import { ClientGrpc } from '@nestjs/microservices';
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { ThemeProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import { ThemeRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { CustomerThemeRequest, CustomerThemeResponse } from './dtos/customers';
import DadosferaLogger from '@dadosfera/dadosfera-logs';
import { resolve } from 'path';
import { Readable } from 'stream';
type Files = {
logoWhite: Express.Multer.File,
logoBlack: Express.Multer.File,
}
// This function will accept any string, which may result in a bug.
@Injectable()
export class ThemeService implements OnModuleInit {
private themeService: ThemeProtoService;
private logger: DadosferaLogger;
constructor(
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
) {
this.logger = dadosferaLogger.logger;
}
onModuleInit() {
this.themeService = this.grpcClient.getService<ThemeProtoService>(
ProtoServices.ThemeProtoService,
);
}
async createThemeByCustomer(id: string, theme: CustomerThemeRequest & Files) {
if (!id) {
this.logger.error('Error - saveCustomertheme - not found id:' + id);
throw new HttpException(null, HttpStatus.BAD_REQUEST);
}
const customerThemeRequest$ = new ReplaySubject<ThemeRequest>();
customerThemeRequest$.next({
customerId: id,
displayName: theme.displayName,
backgroundColor: theme.backgroundColor,
textColor: theme.textColor,
isMetadata: true,
filename: '',
chunk: Buffer.alloc(0)
})
if(theme.logoBlack) {
await this.sendFile(theme.logoBlack, customerThemeRequest$);
}
if(theme.logoWhite) {
await this.sendFile(theme.logoWhite, customerThemeRequest$);
}
customerThemeRequest$.complete();
const stream = this.themeService.CustomerCreateTheme(customerThemeRequest$);
return lastValueFrom(stream);
}
async getThemeByCustomer(id: string): Promise<CustomerThemeResponse> {
if (!id) {
this.logger.error('Error - getCustomerTheme - not found id:' + id);
throw new HttpException(null, HttpStatus.BAD_REQUEST);
}
const { theme } = await firstValueFrom(
this.themeService.CustomerGetTheme({
id
}),
);
return {
theme
}
}
private async sendFile(file: Express.Multer.File, stream$: ReplaySubject<ThemeRequest>) {
const bufferStream = new Readable({
highWaterMark: 1024 * 1024, // 1 MB por chunk
read() {}
});
bufferStream.push(file.buffer);
bufferStream.push(null);
return new Promise((resolve, reject) => {
bufferStream.on('data', (chunk) => {
console.log('enviando chunk', file.fieldname)
const mimetype = file.mimetype.split('/')[1]; // example image/jpeg
const filename = file.fieldname.concat(".", mimetype);
stream$.next({
customerId: '',
displayName: '',
backgroundColor: '',
textColor: '',
isMetadata: false,
filename: filename,
chunk: chunk
});
});
bufferStream.on('end', () => {
console.log('terminou de enviar')
resolve(file.filename)
});
bufferStream.on('error', (err) => {
console.error('Erro no stream:', err);
reject(err);
});
});
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { Info } from '@dadosfera/protospack/dist/lib/interfaces'; import { Info } from 'protospack/dist/lib/interfaces';
export interface ICreateTransformationsRequest { export interface ICreateTransformationsRequest {
transformations: Transformation[]; transformations: Transformation[];
@@ -6,9 +6,7 @@ import {
import { credentials } from '@grpc/grpc-js'; import { credentials } from '@grpc/grpc-js';
import { Transformation } from '@dadosfera/protospack-v2'; import { Transformation } from '@dadosfera/protospack-v2';
const isLocalConnection = const isLocalConnection = !!process.env.INFACTORY_URL?.includes('0.0.0.0');
process.env.INFACTORY_URL.startsWith('in-factory:') ||
process.env.INFACTORY_URL.includes('0.0.0.0');
export class TransformationsClientConfiguration { export class TransformationsClientConfiguration {
public name = 'TransformationsClientConfiguration'; public name = 'TransformationsClientConfiguration';
+13
View File
@@ -263,4 +263,17 @@ export class UsersController {
this.logger.info('synchronizeUser', { user }); this.logger.info('synchronizeUser', { user });
return await this.userService.synchronizeUser(id); return await this.userService.synchronizeUser(id);
} }
@Post('connector_request')
@RequireAllPermissions(PERMISSIONS_GROUPS.DADOSFERA.permissions.SYNC_USER)
@ApiOkResponse({ type: UpdateUserRes })
async sendConnectorRequest(@User() user: RequestUser, request) {
const metadata = PackTheMetadata({
customer_name: user.customer_name,
customer_id: user.customer_id,
user_id: user.user_id,
username: user.username,
});
this.userService.sendUserConnectorRequest(request, metadata);
}
} }
+13
View File
@@ -243,4 +243,17 @@ export class UsersService implements OnModuleInit {
this.usersClientService.INTERNAL_UserSynchronize({ userId }), this.usersClientService.INTERNAL_UserSynchronize({ userId }),
); );
} }
async sendUserConnectorRequest(
request: {
connector_name: string;
description: string;
title: string;
urgency: string;
},
metadata: Metadata,
) {
return await lastValueFrom(
this.usersClientService.UserConnectorRequest({ request }, metadata),
);
}
} }
-10
View File
@@ -1,10 +0,0 @@
const availableLocalEnvs: Array<typeof process.env.ENV> = ['stg', 'test'];
export default function getEnv(): typeof process.env.ENV {
if (process.env.ENV === 'local') {
if (availableLocalEnvs.includes(process.env.LOCAL_ENV))
return process.env.LOCAL_ENV;
return 'stg';
}
return process.env.ENV;
}