diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f71d05..4193f15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,30 +11,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install Docker Compose - run: | - python3 -m pip install docker-compose --upgrade - - name: Build env: - ENV: test - IMAGE_TAG: test + APP_NAME: ${{ github.event.repository.name }} run: | - export ENV=test - export IMAGE_TAG=test - export ACCOUNT_ID=test - docker-compose -f build.docker-compose.yml build + docker build -t $APP_NAME --target test . - name: Run Test env: ENV: test - IMAGE_TAG: test - ACCOUNT_ID: test APP_NAME: ${{ github.event.repository.name }} run: | - export ENV=test - export IMAGE_TAG=test - docker-compose -f build.docker-compose.yml run --rm --entrypoint="npm run test" $APP_NAME + docker run --rm --entrypoint="npm" $APP_NAME run test - name: Remove Docker's Trash if: always() diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ec5331d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug NestJs", + "request": "launch", + "runtimeArgs": [ + "run", + "start:debug" + ], + "runtimeExecutable": "npm", + "skipFiles": [ + "/**" + ], + "type": "node", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ba242b6..2f70641 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,21 @@ +# install all dependencies FROM node:18.3.0-alpine3.15 as packages -WORKDIR /packages -COPY package.json . -COPY package-lock.json . -RUN apk update \ - && apk add --no-cache aws-cli \ - && aws codeartifact login --tool npm --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \ +WORKDIR /app +COPY package.json package-lock.json ./ +RUN apk add --no-cache aws-cli \ + && aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \ && npm install +# unit test specific build +FROM node:18.3.0-alpine3.15 as test +WORKDIR /app +COPY --from=packages /app/node_modules ./node_modules +COPY . ./ + FROM node:18.3.0-alpine3.15 WORKDIR /app COPY . /app/ -COPY --from=packages /packages/node_modules /app/node_modules +COPY --from=packages /app/node_modules ./node_modules RUN npm run build EXPOSE 3333 ENTRYPOINT npm run start diff --git a/README.md b/README.md index 12972d2..8caeff1 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ This decorator requires that **all permissions** listed are granted to the reque ```ts import { RequireAllPermissions } from '../../authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; @Controller('foo') class FooController { @@ -129,7 +129,7 @@ In the following case, the user is required to have **at least one** listed perm ```ts import { RequireSomePermission } from '../../authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; @Controller('foo') @RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.CREATE) @@ -184,7 +184,7 @@ class FooController { RequireSomePermission, AuthenticateCondition, } from '../../authentication/authentication.decorator'; - import { Permissions } from '../../authentication/permissions.enum'; + import { PERMISSIONS } from '../../authentication/permissions.enum'; import { Waa, Baz } from './authenticationFunctions' @Controller('foo') diff --git a/nest-cli.json b/nest-cli.json index 2edc9db..69ccf04 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -3,7 +3,11 @@ "sourceRoot": "src", "compilerOptions": { "assets": [ - "**/*.proto" + "**/*.proto", + { + "include": "i18n/**/*", + "watchAssets": true + } ], "plugins": [ "@nestjs/swagger" diff --git a/package-lock.json b/package-lock.json index f781a6d..9cf4444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "cron-parser": "^4.4.0", - "dadosfera-logs": "^1.0.0-alpha.10", "dotenv": "^14.3.2", "elastic-apm-node": "^3.36.0", "helmet": "^5.1.0", @@ -1226,11 +1225,12 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.1", + "version": "0.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.0", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" }, @@ -1695,15 +1695,6 @@ "node": ">=12" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -1714,10 +1705,20 @@ "kuler": "^2.0.0" } }, + "node_modules/@dadosfera/dadosfera-logs": { + "version": "1.0.0-beta.3", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/dadosfera-logs/-/dadosfera-logs-1.0.0-beta.3.tgz", + "integrity": "sha512-VuGiQlpDulcl1+FSFoT4/6PA9Pr+DAzbFHAYesz9rPinR46ejG7ZBZk3m86NYqQtgMyurqXvgCR8GPy9FpwqFw==", + "dependencies": { + "@elastic/ecs-winston-format": "^1.3.1", + "winston": "^3.7.2", + "winston-log2gelf": "^2.4.0" + } + }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.10.5", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.10.5.tgz", - "integrity": "sha512-A691jkvpnJYtKWyAKlALghlwUqw83dcyarchtXY7hUkd/5SE2D1UHvNE/2IW2zjqDVRS08XMWABrBfw9a9cYOg==", + "version": "3.10.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.10.2.tgz", + "integrity": "sha512-21rbav6VXVo+7Io82/O+350DLmf8ONQw8kkG728F7ySD9uEosJgTz3hzZFpTmVuSew822zS+iHcpGJBpVmq49w==", "dependencies": { "@grpc/grpc-js": "^1.6.7", "rxjs": "^7.5.5", @@ -2260,11 +2261,12 @@ } }, "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.1", + "version": "0.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.0", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" }, @@ -2278,9 +2280,10 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.13", + "version": "0.3.9", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3491,9 +3494,10 @@ } }, "node_modules/acorn": { - "version": "8.7.1", + "version": "8.8.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3906,14 +3910,6 @@ ], "license": "MIT" }, - "node_modules/base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -4579,16 +4575,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dadosfera-logs": { - "version": "1.0.0-alpha.10", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/dadosfera-logs/-/dadosfera-logs-1.0.0-alpha.10.tgz", - "integrity": "sha512-OZ5sAkELiEQrViSvHjxM/Vb/skiXnkimNfgExdKPrHuVfy2s46d47p0QkL5XabFCvjEGyRKUCK8+UL/kRsKnnw==", - "dependencies": { - "@elastic/ecs-winston-format": "^1.3.1", - "winston": "^3.7.2", - "winston-log2gelf": "^2.4.0" - } - }, "node_modules/data-urls": { "version": "2.0.0", "dev": true, @@ -5952,6 +5938,17 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "license": "MIT" @@ -6082,9 +6079,10 @@ "license": "BSD-2-Clause" }, "node_modules/globals": { - "version": "13.15.0", + "version": "13.17.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -8683,19 +8681,6 @@ "node": ">= 0.4.0" } }, - "node_modules/passport-forcedotcom/node_modules/passport-oauth2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.3.0.tgz", - "integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==", - "dependencies": { - "oauth": "0.9.x", - "passport-strategy": "1.x.x", - "uid2": "0.0.x" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/passport-google-oauth20": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", @@ -8766,22 +8751,16 @@ } }, "node_modules/passport-oauth2": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz", - "integrity": "sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==", + "version": "1.3.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/passport-oauth2/-/passport-oauth2-1.3.0.tgz", + "integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==", "dependencies": { - "base64url": "3.x.x", "oauth": "0.9.x", "passport-strategy": "1.x.x", - "uid2": "0.0.x", - "utils-merge": "1.x.x" + "uid2": "0.0.x" }, "engines": { "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" } }, "node_modules/passport-strategy": { @@ -9356,8 +9335,9 @@ } }, "node_modules/rxjs": { - "version": "7.5.5", - "license": "Apache-2.0", + "version": "7.5.6", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", "dependencies": { "tslib": "^2.1.0" } @@ -11955,10 +11935,12 @@ }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.1", + "version": "0.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.0", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" } @@ -12255,16 +12237,6 @@ "dev": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } } }, "@dabh/diagnostics": { @@ -12277,10 +12249,20 @@ "kuler": "^2.0.0" } }, + "@dadosfera/dadosfera-logs": { + "version": "1.0.0-beta.3", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/dadosfera-logs/-/dadosfera-logs-1.0.0-beta.3.tgz", + "integrity": "sha512-VuGiQlpDulcl1+FSFoT4/6PA9Pr+DAzbFHAYesz9rPinR46ejG7ZBZk3m86NYqQtgMyurqXvgCR8GPy9FpwqFw==", + "requires": { + "@elastic/ecs-winston-format": "^1.3.1", + "winston": "^3.7.2", + "winston-log2gelf": "^2.4.0" + } + }, "@dadosfera/protospack-v2": { - "version": "3.10.5", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.10.5.tgz", - "integrity": "sha512-A691jkvpnJYtKWyAKlALghlwUqw83dcyarchtXY7hUkd/5SE2D1UHvNE/2IW2zjqDVRS08XMWABrBfw9a9cYOg==", + "version": "3.10.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.10.2.tgz", + "integrity": "sha512-21rbav6VXVo+7Io82/O+350DLmf8ONQw8kkG728F7ySD9uEosJgTz3hzZFpTmVuSew822zS+iHcpGJBpVmq49w==", "requires": { "@grpc/grpc-js": "^1.6.7", "rxjs": "^7.5.5", @@ -12668,10 +12650,12 @@ }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.1", + "version": "0.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.0", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" } @@ -12683,7 +12667,9 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.13", + "version": "0.3.9", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", @@ -13506,7 +13492,9 @@ } }, "acorn": { - "version": "8.7.1", + "version": "8.8.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true }, "acorn-globals": { @@ -13786,11 +13774,6 @@ "version": "1.5.1", "dev": true }, - "base64url": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", - "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" - }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -14253,16 +14236,6 @@ } } }, - "dadosfera-logs": { - "version": "1.0.0-alpha.10", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/dadosfera-logs/-/dadosfera-logs-1.0.0-alpha.10.tgz", - "integrity": "sha512-OZ5sAkELiEQrViSvHjxM/Vb/skiXnkimNfgExdKPrHuVfy2s46d47p0QkL5XabFCvjEGyRKUCK8+UL/kRsKnnw==", - "requires": { - "@elastic/ecs-winston-format": "^1.3.1", - "winston": "^3.7.2", - "winston-log2gelf": "^2.4.0" - } - }, "data-urls": { "version": "2.0.0", "dev": true, @@ -15204,6 +15177,13 @@ "fs.realpath": { "version": "1.0.0" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1" }, @@ -15282,7 +15262,9 @@ "dev": true }, "globals": { - "version": "13.15.0", + "version": "13.17.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -17023,18 +17005,6 @@ "integrity": "sha512-raDRQqBJtTEHDxLkxLG+9sYE0xpPy9PqriMPbMy9jmpIcU2+mOq9PgczvUPrU8UjfcZp3gqkDHqujcUWiGOVNQ==", "requires": { "passport-oauth2": "1.3.x" - }, - "dependencies": { - "passport-oauth2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.3.0.tgz", - "integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==", - "requires": { - "oauth": "0.9.x", - "passport-strategy": "1.x.x", - "uid2": "0.0.x" - } - } } }, "passport-google-oauth20": { @@ -17090,15 +17060,13 @@ } }, "passport-oauth2": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.6.1.tgz", - "integrity": "sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==", + "version": "1.3.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/passport-oauth2/-/passport-oauth2-1.3.0.tgz", + "integrity": "sha512-AisRXcC7Vx8WSx5C9O8UNib/Y91/IOphs5tImlOMs7T+QTEJdmS+OxgXSskbnDdGQT/8mU5T3yMT6136rBjYXg==", "requires": { - "base64url": "3.x.x", "oauth": "0.9.x", "passport-strategy": "1.x.x", - "uid2": "0.0.x", - "utils-merge": "1.x.x" + "uid2": "0.0.x" } }, "passport-strategy": { @@ -17461,7 +17429,9 @@ } }, "rxjs": { - "version": "7.5.5", + "version": "7.5.6", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", "requires": { "tslib": "^2.1.0" } diff --git a/package.json b/package.json index 735c684..e66072f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "cron-parser": "^4.4.0", - "dadosfera-logs": "^1.0.0-alpha.10", + "@dadosfera/dadosfera-logs": "^1.0.0-beta.3", "dotenv": "^14.3.2", "elastic-apm-node": "^3.36.0", "helmet": "^5.1.0", @@ -98,7 +98,13 @@ "json", "ts" ], - "rootDir": "src", + "rootDir": ".", + "modulePaths": [ + "" + ], + "moduleDirectories": [ + "node_modules" + ], "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" diff --git a/src/app.module.ts b/src/app.module.ts index 10442f0..23c5879 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,12 +1,10 @@ import { Module } from '@nestjs/common'; import { ClientsModule } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; - import { InputsController } from './modules/inputs/inputs.controller'; import { TransformationsController } from './modules/transformations/transformations.controller'; import { OutputsController } from './modules/outputs/outputs.controllers'; import { PipelinesController } from './modules/pipelines/pipelines.controller'; -import { AuthController } from './modules/auth/auth.controller'; import { HealthController } from './modules/health/health.controller'; import { InputsService } from './modules/inputs/inputs.service'; @@ -35,12 +33,14 @@ import { APP_GUARD } from '@nestjs/core'; import { GoogleStrategy } from './modules/oauth/passport-strategies/google-strategy'; import { MailChimpStrategy } from './modules/oauth/passport-strategies/mailchimp-strategy'; import { SalesforceStrategy } from './modules/oauth/passport-strategies/salesforce-strategy'; +import { UsersModule } from './modules/users/users.module'; +import { RolesModule } from './modules/roles/roles.module'; import { ConnectorModule } from './modules/connector/connector.module'; import { AuthModule } from './modules/auth/auth.module'; import { PermissionsModule } from './modules/permissions/permissions.module'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { TermsOfUseModule } from './modules/termsOfUse/termsOfUse.module'; import { ConnectionModule } from './modules/connection/connection.module'; import { ConnectionTestModule } from './modules/connection-test/connection-test.module'; @@ -57,7 +57,6 @@ const transformationClient = new TransformationsClientConfiguration(); TransformationsController, OutputsController, PipelinesController, - AuthController, HealthController, CatalogController, OauthController, @@ -115,6 +114,10 @@ const transformationClient = new TransformationsClientConfiguration(); ...pipelineClient.config(), }, ]), + + UsersModule, + + RolesModule, ], }) export class AppModule {} diff --git a/src/authentication/authentication.decorator.ts b/src/authentication/authentication.decorator.ts index 6c86f13..5bebd81 100644 --- a/src/authentication/authentication.decorator.ts +++ b/src/authentication/authentication.decorator.ts @@ -1,6 +1,6 @@ import { Request } from 'express'; import { CustomDecorator } from '@nestjs/common'; -import { Permission } from './permissions.enum'; +import { PermissionsObjectPermission } from './permissions.enum'; import { RequestUser } from './user.decorator'; export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__'; @@ -31,13 +31,17 @@ function SetMultipleMetadata(metadataKey, metadataValue): CustomDecorator { return decoratorFactory as any; } -export function RequireAllPermissions(...permissions: Permission[]) { +export function RequireAllPermissions( + ...permissions: PermissionsObjectPermission[] +) { return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) => permissions.every(({ seqid }) => user.permissions.includes(seqid)), ); } -export function RequireSomePermission(...permissions: Permission[]) { +export function RequireSomePermission( + ...permissions: PermissionsObjectPermission[] +) { return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) => permissions.some(({ seqid }) => user.permissions.includes(seqid)), ); diff --git a/src/authentication/authentication.guard.spec.ts b/src/authentication/authentication.guard.spec.ts index 5400831..29c6a5f 100644 --- a/src/authentication/authentication.guard.spec.ts +++ b/src/authentication/authentication.guard.spec.ts @@ -5,7 +5,7 @@ import { APP_GUARD } from '@nestjs/core'; import jwt from 'jsonwebtoken'; import { Body, Controller, Get } from '@nestjs/common'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { AuthenticateCondition, Authenticated, @@ -13,11 +13,15 @@ import { RequireSomePermission, } from './authentication.decorator'; import { AuthenticationGuard } from './authentication.guard'; -import { Permissions } from './permissions.enum'; +import { PERMISSIONS } from './permissions.enum'; import { AuthClientService } from '../modules/auth/auth.service'; import ErrorCodes from '../utils/errorCodes'; +const logger = { + info: (...args) => args, + error: (...args) => args, +}; @Controller('no-class-auth') class NoClassAuthController { @Get('body') @@ -32,19 +36,25 @@ class NoClassAuthController { } @Get('required-permission') - @RequireSomePermission(Permissions.ZENDESK.OPEN) + @RequireSomePermission(PERMISSIONS.ZENDESK.permissions.OPEN) async requiredPermission(@Body() body) { return { body }; } @Get('has-all-permissions') - @RequireAllPermissions(Permissions.ZENDESK.OPEN, Permissions.METABASE.OPEN) + @RequireAllPermissions( + PERMISSIONS.ZENDESK.permissions.OPEN, + PERMISSIONS.METABASE.permissions.OPEN, + ) async hasAllPermissions(@Body() body) { return { body }; } @Get('has-some-permission') - @RequireSomePermission(Permissions.ZENDESK.OPEN, Permissions.METABASE.OPEN) + @RequireSomePermission( + PERMISSIONS.ZENDESK.permissions.OPEN, + PERMISSIONS.METABASE.permissions.OPEN, + ) async hasSomePermission(@Body() body) { return { body }; } @@ -52,7 +62,7 @@ class NoClassAuthController { @Controller('class-auth-condition') @AuthenticateCondition((req) => req.get('x-on-class') === 'ok') -@RequireSomePermission(Permissions.METABASE.OPEN) +@RequireSomePermission(PERMISSIONS.METABASE.permissions.OPEN) class ClassAuthConditionController { @Get('body') async getBody(@Body() body) { @@ -67,7 +77,7 @@ class ClassAuthConditionController { @Get('required-permission') @AuthenticateCondition((req) => req.get('x-on-route') === 'ok') - @RequireSomePermission(Permissions.ZENDESK.OPEN) + @RequireSomePermission(PERMISSIONS.ZENDESK.permissions.OPEN) async requiredPermission(@Body() body) { return { body }; } @@ -104,7 +114,7 @@ describe('authentication.guard', () => { }, { provide: DadosferaLogger, - useValue: { logger: console }, + useValue: { logger }, }, { provide: APP_GUARD, @@ -430,17 +440,17 @@ describe('authentication.guard', () => { NoClassAuthTest(null, null); ClassAuthConditionTest(null, null); - const tokenZ = CreateToken([Permissions.ZENDESK.OPEN]); + const tokenZ = CreateToken([PERMISSIONS.ZENDESK.permissions.OPEN]); NoClassAuthTest(tokenZ, ['zendesk']); ClassAuthConditionTest(tokenZ, ['zendesk']); - const tokenM = CreateToken([Permissions.METABASE.OPEN]); + const tokenM = CreateToken([PERMISSIONS.METABASE.permissions.OPEN]); NoClassAuthTest(tokenM, ['metabase']); ClassAuthConditionTest(tokenM, ['metabase']); const tokenZM = CreateToken([ - Permissions.ZENDESK.OPEN, - Permissions.METABASE.OPEN, + PERMISSIONS.ZENDESK.permissions.OPEN, + PERMISSIONS.METABASE.permissions.OPEN, ]); NoClassAuthTest(tokenZM, ['zendesk', 'metabase']); ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']); diff --git a/src/authentication/authentication.guard.ts b/src/authentication/authentication.guard.ts index b663a3b..44b3a75 100644 --- a/src/authentication/authentication.guard.ts +++ b/src/authentication/authentication.guard.ts @@ -3,13 +3,12 @@ import { CanActivate, OnApplicationBootstrap, ExecutionContext, - Logger, Inject, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import assert from 'assert'; import jwt from 'jsonwebtoken'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { AuthClientService } from '../modules/auth/auth.service'; import { AuthenticationFunction, @@ -23,7 +22,7 @@ import ErrorCodes from '../utils/errorCodes'; export class AuthenticationGuard implements CanActivate, OnApplicationBootstrap { - logger: any; + logger: DadosferaLogger; pems: Map; @@ -96,7 +95,7 @@ export class AuthenticationGuard );*/ assert( - accessTokenDecoded.payload.token_use === 'access', + accessTokenDecoded?.payload?.token_use === 'access', 'should be an access token', ); @@ -108,7 +107,7 @@ export class AuthenticationGuard } catch (err) { // log errors if authentication is required if (mustBeAuthenticated) { - this.logger.error('Unable to verify duc access token\n' + err.stack); + this.logger.error('Unable to verify duc access token\n' + err?.stack); } return false; @@ -122,6 +121,7 @@ export class AuthenticationGuard customer_id: accessTokenPayload.customer_id, customer_name: accessTokenPayload.customer_name, customer_tier: accessTokenPayload.customer_tier, + access_token: accessToken, }; // TODO: for backwards compatibility. remove in the future request.body.info = { diff --git a/src/authentication/permissions.enum.ts b/src/authentication/permissions.enum.ts index 2c8a47d..f2e73ea 100644 --- a/src/authentication/permissions.enum.ts +++ b/src/authentication/permissions.enum.ts @@ -1,5 +1,6 @@ import { Logger } from '@nestjs/common'; import assert from 'assert'; +import { LanguageEnum } from 'src/utils/languages.enum'; const logger = new Logger(); @@ -8,10 +9,24 @@ export enum PermissionUsages { INTERNAL = 'internal', } -export interface Permission { +export interface PermissionsObjectPermission { seqid: number; claim: string; usage: PermissionUsages; + name?: Record; +} +export interface PermissionsObjectPermissionTranslated { + seqid: number; + claim: string; + usage: PermissionUsages; + name?: string; +} +export interface PermissionsObjectGroup { + title: { + 'pt-br': string; + 'en-us': string; + }; + permissions: Record; } /* @@ -22,216 +37,422 @@ export interface Permission { /*export const Permissions: { [P in keyof any]: { [Q in keyof any]: Permission }; } = {*/ -export const Permissions = { +export const PERMISSIONS = { AUTH: { - CREATE: { - seqid: 18, - claim: 'POST /auth', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Autenticação', + 'en-us': 'Authentication', + }, + permissions: { + CREATE: { + seqid: 18, + claim: 'POST /auth', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': '', + 'en-us': '', + }, + }, }, }, PIPELINE: { - CREATE: { - seqid: 23, - claim: 'POST /pipelines', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Coletar | Pipelines', + 'en-us': 'Collect | Pipelines', }, - GET: { - seqid: 13, - claim: 'GET /pipelines', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 29, - claim: 'PUT /pipelines', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 5, - claim: 'DELETE /pipelines', - usage: PermissionUsages.PUBLIC, + permissions: { + CREATE: { + seqid: 23, + claim: 'POST /pipelines', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Criar pipelines', + 'en-us': 'Create pipelines', + }, + }, + GET: { + seqid: 13, + claim: 'GET /pipelines', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Buscar pipelines', + 'en-us': 'Get pipelines', + }, + }, + UPDATE: { + seqid: 29, + claim: 'PUT /pipelines', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Atualizar pipeline', + 'en-us': 'Update pipeline', + }, + }, + DELETE: { + seqid: 5, + claim: 'DELETE /pipelines', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Excluir pipeline', + 'en-us': 'Delete pipeline', + }, + }, }, }, INPUT: { - CREATE: { - seqid: 21, - claim: 'POST /inputs', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Coletar | Conexões', + 'en-us': 'Collect | Connections', }, - GET: { - seqid: 9, - claim: 'GET /inputs', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 27, - claim: 'PUT /inputs', - usage: PermissionUsages.PUBLIC, - }, - UPDATE_PARTIAL: { - seqid: 17, - claim: 'PATCH /inputs', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 3, - claim: 'DELETE /inputs', - usage: PermissionUsages.PUBLIC, - }, - GET_ENTITIES: { - seqid: 10, - claim: 'GET /inputs/available-entities', - usage: PermissionUsages.PUBLIC, + permissions: { + CREATE: { + seqid: 21, + claim: 'POST /inputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Criar conexões', + 'en-us': 'Create connections', + }, + }, + GET: { + seqid: 9, + claim: 'GET /inputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Buscar conexões', + 'en-us': 'Get connections', + }, + }, + UPDATE: { + seqid: 27, + claim: 'PUT /inputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Atualizar conexões', + 'en-us': 'Create connections', + }, + }, + DELETE: { + seqid: 3, + claim: 'DELETE /inputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir conexões', + 'en-us': 'Delete connections', + }, + }, + GET_ENTITIES: { + seqid: 10, + claim: 'GET /inputs/available-entities', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir conexões', + 'en-us': 'Delete connections', + }, + }, }, }, OUTPUT: { - CREATE: { - seqid: 22, - claim: 'POST /outputs', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Output', + 'en-us': 'Output', }, - GET: { - seqid: 12, - claim: 'GET /outputs', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 28, - claim: 'PUT /outputs', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 4, - claim: 'DELETE /outputs', - usage: PermissionUsages.PUBLIC, + permissions: { + CREATE: { + seqid: 22, + claim: 'POST /outputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Criar outputs', + 'en-us': 'Create outputs', + }, + }, + GET: { + seqid: 12, + claim: 'GET /outputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Buscar outputs', + 'en-us': 'Get outputs', + }, + }, + UPDATE: { + seqid: 28, + claim: 'PUT /outputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Editar outputs', + 'en-us': 'Edit outputs', + }, + }, + DELETE: { + seqid: 4, + claim: 'DELETE /outputs', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir outputs', + 'en-us': 'Remove outputs', + }, + }, }, }, TRANSFORMATIONS: { - CREATE: { - seqid: 24, - claim: 'POST /transformations', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Micro-transformações', + 'en-us': 'Micro-transformations', }, - GET: { - seqid: 15, - claim: 'GET /transformations', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 30, - claim: 'PUT /transformations', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 6, - claim: 'DELETE /transformations', - usage: PermissionUsages.PUBLIC, + permissions: { + CREATE: { + seqid: 24, + claim: 'POST /transformations', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Criar micro-transformações', + 'en-us': 'Create micro-transformations', + }, + }, + GET: { + seqid: 15, + claim: 'GET /transformations', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Buscar micro-transformações', + 'en-us': 'Get micro-transformations', + }, + }, + UPDATE: { + seqid: 30, + claim: 'PUT /transformations', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Editar micro-transformações', + 'en-us': 'Edit micro-transformations', + }, + }, + DELETE: { + seqid: 6, + claim: 'DELETE /transformations', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir micro-transformações', + 'en-us': 'Remove micro-transformations', + }, + }, }, }, CATALOG: { - CREATE: { - seqid: 19, - claim: 'POST /catalog', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Explorar | Catálogo', + 'en-us': 'Explore | Catalog', }, - GET: { - seqid: 7, - claim: 'GET /catalog', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 25, - claim: 'PUT /catalog', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 1, - claim: 'DELETE /catalog', - usage: PermissionUsages.PUBLIC, + permissions: { + GET: { + seqid: 7, + claim: 'GET /catalog', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Buscar data assets', + 'en-us': 'Search for data assets', + }, + }, + CREATE: { + seqid: 19, + claim: 'POST /catalog', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Criar atributos no catálogo', + 'en-us': 'Create attributes', + }, + }, + UPDATE: { + seqid: 25, + claim: 'PUT /catalog', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Editar atributos no catálogo', + 'en-us': 'Edit attributes', + }, + }, + DELETE: { + seqid: 1, + claim: 'DELETE /catalog', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir atributos no catálogo', + 'en-us': 'Remove attributes', + }, + }, }, }, CONNECTORS: { - CREATE: { - seqid: 20, - claim: 'POST /connectors', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Conectores', + 'en-us': 'Connectors', }, - GET: { - seqid: 8, - claim: 'GET /connectors', - usage: PermissionUsages.PUBLIC, - }, - UPDATE: { - seqid: 26, - claim: 'PUT /connectors', - usage: PermissionUsages.PUBLIC, - }, - DELETE: { - seqid: 2, - claim: 'DELETE /connectors', - usage: PermissionUsages.PUBLIC, + permissions: { + CREATE: { + seqid: 20, + claim: 'POST /connectors', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Criar conectores', + 'en-us': 'Create connectors', + }, + }, + GET: { + seqid: 8, + claim: 'GET /connectors', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Buscar conectores', + 'en-us': 'Get connectors', + }, + }, + UPDATE: { + seqid: 26, + claim: 'PUT /connectors', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Editar conectores', + 'en-us': 'Edit connectors', + }, + }, + DELETE: { + seqid: 2, + claim: 'DELETE /connectors', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Excluir conectores', + 'en-us': 'Remove connectors', + }, + }, }, }, SNOWFLAKE: { - OPEN: { - seqid: 14, - claim: 'GET /snowflake', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Explorar | Consolidar', + 'en-us': 'Explore | Data consolidation', + }, + permissions: { + OPEN: { + seqid: 14, + claim: 'GET /snowflake', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Acessar Snowflake', + 'en-us': 'Access Snowflake', + }, + }, }, }, ZENDESK: { - OPEN: { - seqid: 16, - claim: 'GET /zendesk', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Zendesk', + 'en-us': 'Zendesk', + }, + permissions: { + OPEN: { + seqid: 16, + claim: 'GET /zendesk', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Acessar Zendesk', + 'en-us': 'Access Zendesk', + }, + }, }, }, METABASE: { - OPEN: { - seqid: 11, - claim: 'GET /metabase', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Analisar | Visualização', + 'en-us': 'Analyze | Visualization', + }, + permissions: { + OPEN: { + seqid: 11, + claim: 'GET /metabase', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Acessar Metabase', + 'en-us': 'Access Metabase', + }, + }, }, }, ORCHEST: { - INTELLIGENCE: { - seqid: 31, - claim: 'intelligence:open', - usage: PermissionUsages.PUBLIC, + title: { + 'pt-br': 'Analisar | Inteligência', + 'en-us': 'Analyze | Intelligence', }, - APP_BUILDER: { - seqid: 32, - claim: 'app-builder:open', - usage: PermissionUsages.PUBLIC, + permissions: { + INTELLIGENCE: { + seqid: 31, + claim: 'intelligence:open', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Acessar Orchest', + 'en-us': 'Access Orchest', + }, + }, + APP_BUILDER: { + seqid: 32, + claim: 'app-builder:open', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Acessar App Builder', + 'en-us': 'Access App Builder', + }, + }, + MACHINE_LEARNING: { + seqid: 33, + claim: 'machine-learning:open', + usage: PermissionUsages.INTERNAL, + name: { + 'pt-br': 'Acessar Machine Learning', + 'en-us': 'Access Machine Learning', + }, + }, }, - MACHINE_LEARNING: { - seqid: 33, - claim: 'machine-learning:open', - usage: PermissionUsages.PUBLIC, + }, + USERS: { + title: { + 'pt-br': 'Usuários', + 'en-us': 'Users', + }, + permissions: { + ADMIN: { + seqid: 34, + claim: 'users:admin', + usage: PermissionUsages.PUBLIC, + name: { + 'pt-br': 'Gestão de usuários e grupos', + 'en-us': 'Manage user and roles', + }, + }, }, }, }; // traverses the object searching for duplicate seqids or claims (executes at runtime) let nextAvailableSeqid = 0; -const seqids = Object.values(Permissions).flatMap((namespace) => - Object.values(namespace).map(({ seqid }) => seqid), +const seqids = Object.values(PERMISSIONS).flatMap((namespace) => + Object.values(namespace.permissions).map(({ seqid }) => seqid), ); -const claims = Object.values(Permissions).flatMap((namespace) => - Object.values(namespace).map(({ claim }) => claim), +const claims = Object.values(PERMISSIONS).flatMap((namespace) => + Object.values(namespace.permissions).map(({ claim }) => claim), ); -Object.values(Permissions).map((namespace) => - Object.values(namespace).map(({ seqid, claim }) => { +Object.values(PERMISSIONS).map((namespace) => + Object.values(namespace.permissions).map(({ seqid, claim }) => { const seqidsCount = seqids.filter((x) => x === seqid).length; assert(seqidsCount === 1, `seqid ${seqid} count is not 1`); diff --git a/src/authentication/user.decorator.spec.ts b/src/authentication/user.decorator.spec.ts index cc57a88..8e844f2 100644 --- a/src/authentication/user.decorator.spec.ts +++ b/src/authentication/user.decorator.spec.ts @@ -5,14 +5,18 @@ import { APP_GUARD } from '@nestjs/core'; import jwt from 'jsonwebtoken'; import { Controller, Get } from '@nestjs/common'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { AuthenticationGuard } from './authentication.guard'; import { AuthClientService } from '../modules/auth/auth.service'; import ErrorCodes from '../utils/errorCodes'; import { User } from './user.decorator'; -import { Permissions } from './permissions.enum'; +import { PERMISSIONS } from './permissions.enum'; +const logger = { + info: (...args) => args, + error: (...args) => args, +}; @Controller('user') class UserController { @Get() @@ -45,10 +49,13 @@ describe('user.decorator', () => { const fakeUserPayload = { user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125', username: 'super.admin', - permissions: [Permissions.METABASE.OPEN].map(({ seqid }) => seqid), + permissions: [PERMISSIONS.METABASE.permissions.OPEN].map( + ({ seqid }) => seqid, + ), customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11', customer_name: 'dadosfera', customer_tier: 'BASIC', + access_token: '', }; beforeAll(async () => { @@ -64,7 +71,7 @@ describe('user.decorator', () => { }, { provide: DadosferaLogger, - useValue: { logger: console }, + useValue: { logger }, }, { provide: APP_GUARD, @@ -170,5 +177,6 @@ describe('user.decorator', () => { UserTest(null); const token = CreateToken(); + fakeUserPayload.access_token = token; UserTest(token); }); diff --git a/src/authentication/user.decorator.ts b/src/authentication/user.decorator.ts index 31041ff..cf7709b 100644 --- a/src/authentication/user.decorator.ts +++ b/src/authentication/user.decorator.ts @@ -10,6 +10,7 @@ export interface RequestUser { customer_id: string; customer_name: string; customer_tier: string; + access_token: string; } export const User: (options?: { required?: boolean }) => ParameterDecorator = diff --git a/src/clients/inputs/client.service.ts b/src/clients/inputs/client.service.ts index 0f4c94c..c6da694 100644 --- a/src/clients/inputs/client.service.ts +++ b/src/clients/inputs/client.service.ts @@ -24,14 +24,14 @@ import { objectSnakeToCamel, } from 'src/utils/CaseConverter'; import { mustache } from 'src/utils/mustache'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { OauthSecrets } from 'src/utils/OauthSecrets'; import { URLSearchParams } from 'url'; import { IIdRequest, UpdateInputRequest } from './interfaces'; export class InputsClientService implements OnModuleInit { private inputService: InputService; - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/clients/outputs/client.service.ts b/src/clients/outputs/client.service.ts index 7f0b959..71fbccb 100644 --- a/src/clients/outputs/client.service.ts +++ b/src/clients/outputs/client.service.ts @@ -1,10 +1,7 @@ import { Controller, Inject, OnModuleInit } from '@nestjs/common'; import { ClientGrpc, Payload } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; -import { - OutputServicesNames, - OutputsServiceInterface, -} from 'protospack'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { OutputServicesNames, OutputsServiceInterface } from 'protospack'; import { objectCamelToSnake, objectSnakeToCamel, @@ -18,7 +15,7 @@ import { @Controller('output') export class OutputsClientService implements OnModuleInit { private outputService: OutputsServiceInterface; - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/clients/pipelines/client.config.ts b/src/clients/pipelines/client.config.ts index 01083eb..3f5b72d 100644 --- a/src/clients/pipelines/client.config.ts +++ b/src/clients/pipelines/client.config.ts @@ -1,8 +1,5 @@ import { ClientOptions, Transport } from '@nestjs/microservices'; -import { - PipelinePackages, - PipelineProtoFilePath, -} from 'protospack'; +import { PipelinePackages, PipelineProtoFilePath } from 'protospack'; import { credentials } from '@grpc/grpc-js'; export class PipelinesClientConfiguration { diff --git a/src/clients/pipelines/client.service.ts b/src/clients/pipelines/client.service.ts index c197ca7..e0631d9 100644 --- a/src/clients/pipelines/client.service.ts +++ b/src/clients/pipelines/client.service.ts @@ -17,11 +17,11 @@ import { IIdRequest, IUpdatePipelineRequest, } from './interfaces'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; export class PipelinesClientService implements OnModuleInit { private pipelineService: PipelinesServiceInterface; - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, @@ -255,7 +255,7 @@ export class PipelinesClientService implements OnModuleInit { reject(err); }, complete() { - this.logger.info('Done'); + //this.logger.info('Done'); }, }); }) diff --git a/src/clients/transformations/client.service.ts b/src/clients/transformations/client.service.ts index ea00f88..8f70965 100644 --- a/src/clients/transformations/client.service.ts +++ b/src/clients/transformations/client.service.ts @@ -1,7 +1,7 @@ import { Controller, Inject, OnModuleInit, Post } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { TransformationsServiceInterface } from 'protospack'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { objectCamelToSnake, objectSnakeToCamel, @@ -11,7 +11,7 @@ import { ICreateTransformationsRequest, IIdRequest } from './interfaces'; @Controller('transformation') export class TransformationsClientService implements OnModuleInit { private transformationService: TransformationsServiceInterface; - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/error/grpc-to-http-exception.filter.ts b/src/error/grpc-to-http-exception.filter.ts index 3e187db..f27ed9a 100644 --- a/src/error/grpc-to-http-exception.filter.ts +++ b/src/error/grpc-to-http-exception.filter.ts @@ -1,16 +1,32 @@ -import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + HttpStatus, +} from '@nestjs/common'; import { Response } from 'express'; import ErrorBuilder from '../utils/ErrorBuilder'; +/** + * ExceptionFilter to handle RpcException thrown by our microservices + * + * If it detects that the exception is already an **HttpException** or it is generated by our **ErrorBuilder**, + * it will return the exception without any treatment. + */ @Catch(Error) export class GrpcToHttpExceptionFilter implements ExceptionFilter { catch(exception: any, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - // return this exception directly if is already of type ErrorBuilder, - // else transform it using the ErrorBuilder + if (exception instanceof HttpException) { + return response + .status(exception.getStatus()) + .json(exception.getResponse()); + } + const err = exception.constructor.name === ErrorBuilder.name ? exception diff --git a/src/main.ts b/src/main.ts index 788cf4c..7f60009 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ if (process.env.ENV !== 'local') require('elastic-apm-node/start'); import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import helmet from 'helmet'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import documentEmpty from '../swagger_empty.json'; @@ -12,21 +12,10 @@ async function bootstrap() { serviceName: 'maestro', serviceEnvironment: process.env.ENV, }); - const logger = new DadosferaLogger().logger; - - const orginalWinstonLog = logger.log.bind(logger); - logger.log = function (...kwargs: any[]) { - if (kwargs[0] in logger.levels) { - if (kwargs[0] == 'error') { - orginalWinstonLog('error', kwargs[2]); - } - } else { - orginalWinstonLog('info', ...kwargs); - } - }; + const logger = new DadosferaLogger(); const app = await NestFactory.create(AppModule, { - logger: logger, + logger, cors: { origin: '*', methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', @@ -42,6 +31,11 @@ async function bootstrap() { .setDescription('Documentation for Maestro gateway') .setVersion('1.0') .addBearerAuth() + .addServer( + process.env.ENV === 'local' + ? 'http://localhost:3333' + : 'https://maestro.dev.dadosfera.ai', + ) .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 2e4b54c..e664c96 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -22,14 +22,14 @@ import { } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { AuthClientService } from './auth.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter'; @ApiTags('Auth') @UseFilters(new GrpcToHttpExceptionFilter()) @Controller('auth') export class AuthController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 65d6b34..88b2dda 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ClientsModule } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { AuthController } from './auth.controller'; import { AuthClientService } from './auth.service'; diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index de998f9..0da58eb 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,6 +1,6 @@ import { OnModuleInit, Inject, Injectable } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; @@ -20,7 +20,7 @@ import { @Injectable() export class AuthClientService implements OnModuleInit { - logger: any; + logger: DadosferaLogger; private authService: AuthServiceInterface; constructor( diff --git a/src/modules/catalog/catalog.controller.ts b/src/modules/catalog/catalog.controller.ts index 7c44f81..bb7cb84 100644 --- a/src/modules/catalog/catalog.controller.ts +++ b/src/modules/catalog/catalog.controller.ts @@ -10,9 +10,9 @@ import { } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { AuthenticateCondition } from '../../authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; import { CatalogService } from './catalog.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @ApiTags('Catalog') @Controller('catalog') @@ -32,10 +32,12 @@ import { DadosferaLogger } from 'dadosfera-logs'; action = req.method; } - return user.permissions.includes(Permissions.CATALOG[action].seqid); + return user.permissions.includes( + PERMISSIONS.CATALOG.permissions[action].seqid, + ); }) export class CatalogController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/connection-test/connection-test.controller.spec.ts b/src/modules/connection-test/connection-test.controller.spec.ts index efe3fe0..6744a3b 100644 --- a/src/modules/connection-test/connection-test.controller.spec.ts +++ b/src/modules/connection-test/connection-test.controller.spec.ts @@ -1,12 +1,19 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConnectionTestController } from './connection-test.controller'; +import { ConnectionTestService } from './connection-test.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ConnectionTestClientConfiguration } from './connection-test-client.config'; +import { ClientsModule } from '@nestjs/microservices'; describe('ConnectionTestController', () => { let controller: ConnectionTestController; beforeEach(async () => { + const client = new ConnectionTestClientConfiguration(); const module: TestingModule = await Test.createTestingModule({ controllers: [ConnectionTestController], + providers: [ConnectionTestService, DadosferaLogger], + imports: [ClientsModule.register([client.providerOptions])], }).compile(); controller = module.get(ConnectionTestController); diff --git a/src/modules/connection-test/connection-test.controller.ts b/src/modules/connection-test/connection-test.controller.ts index 95ed102..d787fe1 100644 --- a/src/modules/connection-test/connection-test.controller.ts +++ b/src/modules/connection-test/connection-test.controller.ts @@ -5,6 +5,7 @@ import { HttpStatus, Inject, Post, + UseFilters, ValidationPipe, } from '@nestjs/common'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; @@ -22,11 +23,13 @@ import { GetTableMetadataRes, GetTableMetadataReq, } from './dto/connection-test'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { Authenticated } from 'src/authentication/authentication.decorator'; +import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; @ApiTags('Connection Test') @Controller('connection-test') +@UseFilters(new GrpcToHttpExceptionFilter()) @Authenticated() export class ConnectionTestController { logger: any; @@ -60,16 +63,13 @@ export class ConnectionTestController { @HttpCode(HttpStatus.OK) async connectionTestCredentials( @User() user: RequestUser, - @Body(new ValidationPipe()) body: ConnectionTestCredentialsReq, + @Body() body: ConnectionTestCredentialsReq, ) { this.logger.info('/connection-test/credentials', { user: user.user_id, customer: user.customer_name, }); - return this.connectionTestService.connectionTestCredentials( - body, - user.customer_name, - ); + return this.connectionTestService.connectionTestCredentials(body, user); } @Post('schemas') @@ -113,7 +113,7 @@ export class ConnectionTestController { @User() user: RequestUser, @Body(new ValidationPipe()) body: GetTableMetadataReq, ) { - this.logger.info('/connection-test/tables', { + this.logger.info('/connection-test/table-metadata', { user: user.user_id, customer: user.customer_name, }); diff --git a/src/modules/connection-test/connection-test.module.ts b/src/modules/connection-test/connection-test.module.ts index 687df06..1eb4099 100644 --- a/src/modules/connection-test/connection-test.module.ts +++ b/src/modules/connection-test/connection-test.module.ts @@ -1,13 +1,14 @@ import { Module } from '@nestjs/common'; import { ConnectionTestController } from './connection-test.controller'; import { ConnectionTestService } from './connection-test.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { ClientsModule } from '@nestjs/microservices'; import { ConnectionTestClientConfiguration } from './connection-test-client.config'; +import { ConnectionModule } from '../connection/connection.module'; const client = new ConnectionTestClientConfiguration(); @Module({ controllers: [ConnectionTestController], providers: [ConnectionTestService, DadosferaLogger], - imports: [ClientsModule.register([client.providerOptions])], + imports: [ClientsModule.register([client.providerOptions]), ConnectionModule], }) export class ConnectionTestModule {} diff --git a/src/modules/connection-test/connection-test.service.spec.ts b/src/modules/connection-test/connection-test.service.spec.ts index 3b44f65..3550ffc 100644 --- a/src/modules/connection-test/connection-test.service.spec.ts +++ b/src/modules/connection-test/connection-test.service.spec.ts @@ -1,12 +1,19 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { ConnectionTestController } from './connection-test.controller'; import { ConnectionTestService } from './connection-test.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ConnectionTestClientConfiguration } from './connection-test-client.config'; +import { ClientsModule } from '@nestjs/microservices'; describe('ConnectionTestService', () => { let service: ConnectionTestService; beforeEach(async () => { + const client = new ConnectionTestClientConfiguration(); const module: TestingModule = await Test.createTestingModule({ - providers: [ConnectionTestService], + controllers: [ConnectionTestController], + providers: [ConnectionTestService, DadosferaLogger], + imports: [ClientsModule.register([client.providerOptions])], }).compile(); service = module.get(ConnectionTestService); diff --git a/src/modules/connection-test/connection-test.service.ts b/src/modules/connection-test/connection-test.service.ts index 300e765..08fc8c7 100644 --- a/src/modules/connection-test/connection-test.service.ts +++ b/src/modules/connection-test/connection-test.service.ts @@ -14,12 +14,16 @@ import { GetTableMetadataReq, GetTableMetadataRes, } from './dto/connection-test'; +import { ConnectionClientService } from '../connection/client.service'; +import { CreateConnectionDto } from '../connection/dtos/create-connection'; +import { RequestUser } from 'src/authentication/user.decorator'; @Injectable() export class ConnectionTestService { private connectionTestReadClient: ConnectionTest.ReadService.ConnectionTestReadServices; constructor( @Inject('ConnectionTestGrpcClient') private readonly grpcClient: ClientGrpc, + private connectionsService: ConnectionClientService, ) { this.connectionTestReadClient = grpcClient.getService( @@ -44,19 +48,45 @@ export class ConnectionTestService { } async connectionTestCredentials( body: ConnectionTestCredentialsReq, - customer_name: string, + user: RequestUser, ): Promise { - const { connector_id, plugin } = body; - //CREATE CONNECTION - const connection_id = ''; - /***********************/ + const { plugin, ...connectionBody } = body; + let { connection_id } = body; + const { customer_id, customer_name, user_id } = user; + + /********* CREATE CONNECTION *********/ + if (!connection_id) { + if (!connectionBody.properties.credentials_type) + connectionBody.properties.credentials_type = 'basic_auth'; + + const createConnectionBody: CreateConnectionDto = { + ...connectionBody, + }; + const { connection } = await this.connectionsService.createConnection({ + body: createConnectionBody, + info: { customer_id, customer_name, user_id }, + }); + connection_id = connection.id; + } + /************************************/ const { operation_result } = await lastValueFrom( this.connectionTestReadClient.ConnectionTestCredentials({ connection_id, customer_name, plugin, }), - ); + ).catch(async (e) => { + await this.connectionsService.deleteConnection({ + body: { id: connection_id }, + info: { customer_id, customer_name, user_id }, + }); + throw e; + }); + if (!operation_result) + await this.connectionsService.deleteConnection({ + body: { id: connection_id }, + info: { customer_id, customer_name, user_id }, + }); return { operation_result, connection_id }; } async connectionTestListSchemas( diff --git a/src/modules/connection-test/dto/connection-test.ts b/src/modules/connection-test/dto/connection-test.ts index 254f0e6..9d2e5b1 100644 --- a/src/modules/connection-test/dto/connection-test.ts +++ b/src/modules/connection-test/dto/connection-test.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger'; -import { IsString, IsObject, IsOptional } from 'class-validator'; +import { IsString, IsOptional } from 'class-validator'; import { DatabaseConnectionPropertiesDto } from 'src/modules/connection/dtos/connection'; import { CreateConnectionDto } from 'src/modules/connection/dtos/create-connection'; export class ColumnDto { @@ -44,11 +44,13 @@ export class ConnectionTestCredentialsReq extends OmitType( ['properties'], ) { @ApiProperty() - @IsString() plugin: string; + @ApiProperty() - @IsObject() properties: DatabaseConnectionPropertiesDto; + + @ApiPropertyOptional() + connection_id?: string; } export class ConnectionTestCredentialsRes { diff --git a/src/modules/connection/connection.controller.ts b/src/modules/connection/connection.controller.ts index bc5907e..102d9ab 100644 --- a/src/modules/connection/connection.controller.ts +++ b/src/modules/connection/connection.controller.ts @@ -13,12 +13,12 @@ import { import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ConnectionClientService } from './client.service'; import { CreateConnectionDto } from './dtos/create-connection'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { Authenticated, RequireAllPermissions, } from 'src/authentication/authentication.decorator'; -import { Permissions } from 'src/authentication/permissions.enum'; +import { PERMISSIONS } from 'src/authentication/permissions.enum'; import { User } from 'src/authentication/user.decorator'; import { ValidationPipe } from '../../pipes/object-validation.pipe'; import { UpdateConnectionDto } from './dtos/update-connection'; diff --git a/src/modules/connection/connection.module.ts b/src/modules/connection/connection.module.ts index dde02b3..086b72c 100644 --- a/src/modules/connection/connection.module.ts +++ b/src/modules/connection/connection.module.ts @@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices'; import { ConnectionClientService } from './client.service'; import { ConnectionClientConfiguration } from './client.config'; import { ConnectionController } from './connection.controller'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; const client = new ConnectionClientConfiguration(); @@ -18,5 +18,6 @@ const client = new ConnectionClientConfiguration(); }, ]), ], + exports: [ConnectionClientService], }) export class ConnectionModule {} diff --git a/src/modules/connector/connector.controller.ts b/src/modules/connector/connector.controller.ts index 1141ac5..5529d1a 100644 --- a/src/modules/connector/connector.controller.ts +++ b/src/modules/connector/connector.controller.ts @@ -24,19 +24,19 @@ import { DeleteConnectorDto } from './dtos/delete-connector'; import { GetAllDto } from './dtos/get-all'; import { RemoveTagDto } from './dtos/remove-tag'; import { UpdateDto } from './dtos/update'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { Authenticated, RequireAllPermissions, } from 'src/authentication/authentication.decorator'; -import { Permissions } from 'src/authentication/permissions.enum'; +import { PERMISSIONS } from 'src/authentication/permissions.enum'; @ApiTags('connectors') @ApiBearerAuth() @Authenticated() @Controller('connectors') export class ConnectorController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, @@ -46,7 +46,7 @@ export class ConnectorController { } @Post() - @RequireAllPermissions(Permissions.CONNECTORS.CREATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.CREATE) @ApiConsumes('multipart/form-data') @UseInterceptors(FileInterceptor('file')) async uploadConnector( @@ -64,7 +64,7 @@ export class ConnectorController { } @Post('/uploads') - @RequireAllPermissions(Permissions.CONNECTORS.CREATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.CREATE) @ApiConsumes('multipart/form-data') @UseInterceptors(FilesInterceptor('files')) async uploadFile(@UploadedFiles() files) { @@ -81,7 +81,7 @@ export class ConnectorController { } @Post('/multiple-connectors-upload') - @RequireAllPermissions(Permissions.CONNECTORS.CREATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.CREATE) @ApiConsumes('multipart/form-data') @UseInterceptors(FileInterceptor('file')) async uploadConnectorsFile(@UploadedFile() file: UploadConnectorsFileDto) { @@ -95,7 +95,7 @@ export class ConnectorController { } @Get() - @RequireAllPermissions(Permissions.CONNECTORS.GET) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.GET) async getAllConnectors( @Headers('dadosfera-lang') language, @Query() queries: GetAllDto, @@ -121,13 +121,13 @@ export class ConnectorController { } @Get('/tags') - @RequireAllPermissions(Permissions.CONNECTORS.GET) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.GET) async getConnectorsTags() { return await this.connectorClientService.getConnectorsTags(); } @Get('/:plugin') - @RequireAllPermissions(Permissions.CONNECTORS.GET) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.GET) async getConnector( @Headers('dadosfera-lang') language, @Param('plugin') plugin: string, @@ -149,7 +149,7 @@ export class ConnectorController { } @Get('/:plugin/details') - @RequireAllPermissions(Permissions.CONNECTORS.GET) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.GET) async getConnectorDetails( @Headers('dadosfera-lang') language, @Param('plugin') plugin: string, @@ -171,7 +171,7 @@ export class ConnectorController { } @Put('/:plugin') - @RequireAllPermissions(Permissions.CONNECTORS.UPDATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.UPDATE) @ApiConsumes('multipart/form-data') async updateConnector( @Param('plugin') plugin: string, @@ -193,7 +193,7 @@ export class ConnectorController { } @Put('/:plugin/add-tag') - @RequireAllPermissions(Permissions.CONNECTORS.UPDATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.UPDATE) async addTagOnConnector( @Param('plugin') plugin: string, @Body() body: AddTagDto, @@ -220,7 +220,7 @@ export class ConnectorController { } @Put('/:plugin/remove-tag') - @RequireAllPermissions(Permissions.CONNECTORS.UPDATE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.UPDATE) async removeTagOnConnector( @Param('plugin') plugin: string, @Body() body: RemoveTagDto, @@ -248,7 +248,7 @@ export class ConnectorController { } @Delete('/:plugin') - @RequireAllPermissions(Permissions.CONNECTORS.DELETE) + @RequireAllPermissions(PERMISSIONS.CONNECTORS.permissions.DELETE) async deleteConnector( @Param('plugin') plugin: string, @Body() { version }: DeleteConnectorDto, diff --git a/src/modules/connector/connector.module.ts b/src/modules/connector/connector.module.ts index 417380b..d30e59a 100644 --- a/src/modules/connector/connector.module.ts +++ b/src/modules/connector/connector.module.ts @@ -3,7 +3,7 @@ import { ClientsModule } from '@nestjs/microservices'; import { ConnectorClientService } from './client.service'; import { ConnectorClientConfiguration } from './client.config'; import { ConnectorController } from './connector.controller'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; const connectorClient = new ConnectorClientConfiguration(); diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts index 073626f..67f5ffa 100644 --- a/src/modules/health/health.controller.ts +++ b/src/modules/health/health.controller.ts @@ -1,12 +1,12 @@ import { Controller, Get, Inject } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { HealthService } from './health.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @ApiTags('Health') @Controller('health') export class HealthController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/health/health.service.ts b/src/modules/health/health.service.ts index 906981d..3934985 100644 --- a/src/modules/health/health.service.ts +++ b/src/modules/health/health.service.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @Injectable() export class HealthService { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/inputs/inputs.controller.ts b/src/modules/inputs/inputs.controller.ts index 7ac86e3..c464b9d 100644 --- a/src/modules/inputs/inputs.controller.ts +++ b/src/modules/inputs/inputs.controller.ts @@ -9,10 +9,10 @@ import { Post, } from '@nestjs/common'; import { InputsService } from './inputs.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { InputsClientService } from 'src/clients/inputs/client.service'; import { UpdateInputRequest } from 'src/clients/inputs/interfaces'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; import { AuthenticateCondition } from 'src/authentication/authentication.decorator'; import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { @@ -41,7 +41,7 @@ import { break; case 'PATCH': - action = 'UPDATE_PARTIAL'; + action = 'UPDATE'; break; default: @@ -55,11 +55,11 @@ import { action = 'GET_ENTITIES'; } - return user.permissions.includes(Permissions.INPUT[action].seqid); + return user.permissions.includes(PERMISSIONS.INPUT.permissions[action].seqid); }) export class InputsController { inputService: InputsService; - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/inputs/inputs.service.ts b/src/modules/inputs/inputs.service.ts index 29d9750..812e189 100644 --- a/src/modules/inputs/inputs.service.ts +++ b/src/modules/inputs/inputs.service.ts @@ -7,7 +7,7 @@ import { IIdRequest, Info } from 'src/clients/inputs/interfaces'; @Injectable() export class InputsService { - constructor(private inputClient: InputsClientService) { } + constructor(private inputClient: InputsClientService) {} secondsInADay = 60 * 60 * 24; secondsInAnHour = 60 * 60; @@ -54,7 +54,7 @@ export class InputsService { switch (data.plugin.toLowerCase()) { case 'csv': case 'json': - case 'parquet': + case 'parquet': { const { info, ...input } = data; const inputPayload = this.generateInputS3Payload(input); response = await this.inputClient.createS3Inputs({ @@ -62,6 +62,7 @@ export class InputsService { info, }); break; + } case 'oracle': case 'mysql': case 'postgresql': diff --git a/src/modules/network-config/dto/network-config.ts b/src/modules/network-config/dto/network-config.ts index 72146e8..ab974d8 100644 --- a/src/modules/network-config/dto/network-config.ts +++ b/src/modules/network-config/dto/network-config.ts @@ -81,9 +81,18 @@ export class CreateNetworkConfigReq extends PickType(NetworkConfigDto, [ 'description', 'type', 'network_type_id', + 'plugin', ]) { - @ApiProperty() - properties: NetworkConfigOpenVpnProperties; + @ApiProperty({ + type: 'file', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }) + OVPN_FILE: any; } export class CreateNetworkConfigRes { @ApiProperty() diff --git a/src/modules/network-config/network-config.controller.spec.ts b/src/modules/network-config/network-config.controller.spec.ts index 84d229d..dcd7825 100644 --- a/src/modules/network-config/network-config.controller.spec.ts +++ b/src/modules/network-config/network-config.controller.spec.ts @@ -1,12 +1,19 @@ +import { ClientsModule } from '@nestjs/microservices'; import { Test, TestingModule } from '@nestjs/testing'; +import { NetworkConfigClientConfiguration } from './network-config-client.config'; import { NetworkConfigController } from './network-config.controller'; +import { NetworkConfigService } from './network-config.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; describe('NetworkConfigController', () => { let controller: NetworkConfigController; beforeEach(async () => { + const client = new NetworkConfigClientConfiguration(); const module: TestingModule = await Test.createTestingModule({ controllers: [NetworkConfigController], + providers: [NetworkConfigService, DadosferaLogger], + imports: [ClientsModule.register([client.providerOptions])], }).compile(); controller = module.get(NetworkConfigController); diff --git a/src/modules/network-config/network-config.controller.ts b/src/modules/network-config/network-config.controller.ts index 1d489b2..3948f50 100644 --- a/src/modules/network-config/network-config.controller.ts +++ b/src/modules/network-config/network-config.controller.ts @@ -9,10 +9,12 @@ import { Patch, Post, Query, + UploadedFile, + UseInterceptors, } from '@nestjs/common'; import { NetworkConfigService } from './network-config.service'; -import { DadosferaLogger } from 'dadosfera-logs'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ApiConsumes, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { RequestUser, User } from 'src/authentication/user.decorator'; import { CreateNetworkConfigReq, @@ -22,6 +24,7 @@ import { UpdateNetworkConfigReq, UpdateNetworkConfigRes, } from './dto/network-config'; +import { FileInterceptor } from '@nestjs/platform-express'; @ApiTags('Network Config') @Controller('network-config') @@ -87,15 +90,18 @@ export class NetworkConfigController { @Post() @ApiOkResponse({ type: CreateNetworkConfigRes }) + @ApiConsumes('multipart/form-data') + @UseInterceptors(FileInterceptor('OVPN_FILE')) async createNetworkConfigs( @User() user: RequestUser, @Body() body: CreateNetworkConfigReq, + @UploadedFile() file: Express.Multer.File, ) { this.logger.info('POST /network-config', { user: user.user_id, customer: user.customer_name, }); - return this.networkConfigService.createNetworkConfig(body, { + return this.networkConfigService.createNetworkConfig(body, file, { customer_name: user.customer_name, customer_id: user.customer_id, }); diff --git a/src/modules/network-config/network-config.module.ts b/src/modules/network-config/network-config.module.ts index acd8a89..1e701d0 100644 --- a/src/modules/network-config/network-config.module.ts +++ b/src/modules/network-config/network-config.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { NetworkConfigController } from './network-config.controller'; import { NetworkConfigService } from './network-config.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { ClientsModule } from '@nestjs/microservices'; import { NetworkConfigClientConfiguration } from './network-config-client.config'; const client = new NetworkConfigClientConfiguration(); diff --git a/src/modules/network-config/network-config.service.spec.ts b/src/modules/network-config/network-config.service.spec.ts index 0311256..a2390e6 100644 --- a/src/modules/network-config/network-config.service.spec.ts +++ b/src/modules/network-config/network-config.service.spec.ts @@ -1,12 +1,19 @@ +import { ClientsModule } from '@nestjs/microservices'; import { Test, TestingModule } from '@nestjs/testing'; +import { NetworkConfigClientConfiguration } from './network-config-client.config'; +import { NetworkConfigController } from './network-config.controller'; import { NetworkConfigService } from './network-config.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; describe('NetworkConfigService', () => { let service: NetworkConfigService; beforeEach(async () => { + const client = new NetworkConfigClientConfiguration(); const module: TestingModule = await Test.createTestingModule({ - providers: [NetworkConfigService], + controllers: [NetworkConfigController], + providers: [NetworkConfigService, DadosferaLogger], + imports: [ClientsModule.register([client.providerOptions])], }).compile(); service = module.get(NetworkConfigService); diff --git a/src/modules/network-config/network-config.service.ts b/src/modules/network-config/network-config.service.ts index f4cb42a..d54f943 100644 --- a/src/modules/network-config/network-config.service.ts +++ b/src/modules/network-config/network-config.service.ts @@ -88,9 +88,13 @@ export class NetworkConfigService { async createNetworkConfig( body: CreateNetworkConfigReq, + file: Express.Multer.File, { customer_name, customer_id }, ): Promise { - const { description, name, properties, type, network_type_id } = body; + const { description, name, type, network_type_id, plugin } = body; + + const OVPN_FILE_STRING = Buffer.from(file.buffer).toString(); + const meta = new Metadata(); meta.add('customer_id', customer_id); meta.add('customer_name', customer_name); @@ -100,7 +104,10 @@ export class NetworkConfigService { customer_name, description, name, - properties: JSON.stringify(properties), + properties: JSON.stringify({ + OVPN_FILE: OVPN_FILE_STRING, + plugin, + }), type, network_type_id, }, diff --git a/src/modules/outputs/outputs.controllers.ts b/src/modules/outputs/outputs.controllers.ts index e13bf70..588417f 100644 --- a/src/modules/outputs/outputs.controllers.ts +++ b/src/modules/outputs/outputs.controllers.ts @@ -12,9 +12,9 @@ import { Payload } from '@nestjs/microservices'; import { ApiTags } from '@nestjs/swagger'; import { OutputsClientService } from 'src/clients/outputs/client.service'; import { AuthenticateCondition } from 'src/authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; import { OutputsService } from './outputs.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @ApiTags('Outputs') @Controller('outputs') @@ -34,10 +34,12 @@ import { DadosferaLogger } from 'dadosfera-logs'; action = req.method; } - return user.permissions.includes(Permissions.OUTPUT[action].seqid); + return user.permissions.includes( + PERMISSIONS.OUTPUT.permissions[action].seqid, + ); }) export class OutputsController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/permissions/dto/entities.ts b/src/modules/permissions/dto/entities.ts new file mode 100644 index 0000000..faa4a3b --- /dev/null +++ b/src/modules/permissions/dto/entities.ts @@ -0,0 +1,49 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PermissionGroupPermission { + @ApiProperty() + name: string; + @ApiProperty() + id: number; +} + +export class PermissionGroup { + @ApiProperty() + title: string; + @ApiProperty({ type: [PermissionGroupPermission] }) + permissions: PermissionGroupPermission[]; +} + +export class Permission { + @ApiProperty() + id: string; + @ApiProperty() + seqid: number; + @ApiProperty() + name: string; + @ApiProperty() + claim: string; + @ApiProperty() + createdAt: string; + @ApiProperty() + updatedAt: string; +} + +export class PermissionDto { + @ApiProperty() + id: number; + @ApiProperty() + seqid: number; + @ApiProperty() + name: string; + @ApiProperty() + claim: string; + @ApiProperty() + createdAt: string; + @ApiProperty() + updatedAt: string; +} +export class GetPublicPermissionsRes { + @ApiProperty({ type: [PermissionGroup] }) + permissionGroups: PermissionGroup[]; +} diff --git a/src/modules/permissions/permissions.controller.spec.ts b/src/modules/permissions/permissions.controller.spec.ts new file mode 100644 index 0000000..a4db69e --- /dev/null +++ b/src/modules/permissions/permissions.controller.spec.ts @@ -0,0 +1,42 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DucClient } from 'src/clients/duc/client.config'; +import { PermissionsController } from './permissions.controller'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { PermissionsService } from './permissions.service'; + +const ducClient = new DucClient(); +const logger = { + info: (...args) => args, + error: (...args) => args, +}; +describe('PermissionsController', () => { + let controller: PermissionsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + ], + controllers: [PermissionsController], + providers: [ + { + provide: DadosferaLogger, + useValue: { logger }, + }, + PermissionsService, + ], + }).compile(); + + controller = module.get(PermissionsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/permissions/permissions.controller.ts b/src/modules/permissions/permissions.controller.ts new file mode 100644 index 0000000..ba50ce4 --- /dev/null +++ b/src/modules/permissions/permissions.controller.ts @@ -0,0 +1,51 @@ +import { Controller, Get, Headers, Inject } from '@nestjs/common'; +import { ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { + Authenticated, + RequireAllPermissions, +} from 'src/authentication/authentication.decorator'; +import { + PERMISSIONS, + PermissionUsages, +} from 'src/authentication/permissions.enum'; +import { RequestUser, User } from 'src/authentication/user.decorator'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { GetPublicPermissionsRes } from './dto/entities'; +import { PermissionsService } from './permissions.service'; + +@ApiTags('Permissions') +@Controller('permissions') +@ApiHeader({ + name: 'dadosfera-lang', + enum: LanguageEnum, + required: false, + description: 'Defaults to pt-br', +}) +@Authenticated() +export class PermissionsController { + logger: DadosferaLogger; + + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private permissionsService: PermissionsService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Get() + @ApiOkResponse({ type: GetPublicPermissionsRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + getPublicPermissions( + @User() user: RequestUser, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('getPublicPermissions', { user }); + const permissionGroups = this.permissionsService.getPermissionsGrouped( + PermissionUsages.PUBLIC, + language, + ); + return { permissionGroups }; + } +} diff --git a/src/modules/permissions/permissions.module.ts b/src/modules/permissions/permissions.module.ts index f390558..b1155d4 100644 --- a/src/modules/permissions/permissions.module.ts +++ b/src/modules/permissions/permissions.module.ts @@ -1,10 +1,12 @@ import { Module } from '@nestjs/common'; import { ClientsModule } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; -import { PermissionsClientService } from './permissions.service'; +import { PermissionsService } from './permissions.service'; import { DucClient } from '../../clients/duc/client.config'; +import { PermissionsController } from './permissions.controller'; + const ducClient = new DucClient(); @Module({ @@ -16,6 +18,8 @@ const ducClient = new DucClient(); }, ]), ], - providers: [PermissionsClientService, DadosferaLogger], + providers: [PermissionsService, DadosferaLogger], + controllers: [PermissionsController], + exports: [PermissionsService], }) export class PermissionsModule {} diff --git a/src/modules/permissions/permissions.service.ts b/src/modules/permissions/permissions.service.ts index 9887b27..3dfc3d8 100644 --- a/src/modules/permissions/permissions.service.ts +++ b/src/modules/permissions/permissions.service.ts @@ -10,17 +10,24 @@ import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; import { PermissionsProtoService as PermissionsServiceInterface } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { InjectPermissionsRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; -import { DadosferaLogger } from 'dadosfera-logs'; -import { Permissions } from '../../authentication/permissions.enum'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { + PERMISSIONS, + PermissionUsages, + PermissionsObjectGroup, + PermissionsObjectPermissionTranslated, +} from '../../authentication/permissions.enum'; import ErrorBuilder from '../../utils/ErrorBuilder'; +import { PermissionGroupPermission, PermissionGroup } from './dto/entities'; +import { LanguageEnum } from 'src/utils/languages.enum'; @Injectable() -export class PermissionsClientService +export class PermissionsService implements OnModuleInit, OnApplicationBootstrap { - logger: any; + logger: DadosferaLogger; - private permissionsService: PermissionsServiceInterface; + private permissionsClientService: PermissionsServiceInterface; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, @@ -30,7 +37,7 @@ export class PermissionsClientService } onModuleInit() { - this.permissionsService = + this.permissionsClientService = this.grpcClient.getService( ProtoServices.PermissionsProtoService, ); @@ -40,8 +47,8 @@ export class PermissionsClientService async onApplicationBootstrap() { this.logger.info('sending permissions to DUC...'); - const permissions = Object.values(Permissions).flatMap((namespace) => - Object.values(namespace), + const permissions = Object.values(PERMISSIONS).flatMap((namespace) => + Object.values(namespace.permissions), ); return this.injectPermissions({ @@ -64,7 +71,48 @@ export class PermissionsClientService this.logger.info('InjectPermissions'); return lastValueFrom( - this.permissionsService.InjectPermissions({ permissions }), + this.permissionsClientService.InjectPermissions({ permissions }), ); } + getPermissionsGrouped( + permissionUsage?: PermissionUsages, + language: LanguageEnum = LanguageEnum.ptbr, + ): PermissionGroup[] { + const groups: PermissionGroup[] = []; + for (const key in PERMISSIONS) { + const permissionGroup: PermissionsObjectGroup = PERMISSIONS[key]; + const title = permissionGroup.title[language]; + const permissions: PermissionGroupPermission[] = Object.values( + permissionGroup.permissions, + ) + .filter(({ usage }) => + permissionUsage ? usage === permissionUsage : true, + ) + .map(({ seqid, name }) => ({ id: seqid, name: name[language] })); + if (permissions?.length) + groups.push({ + title, + permissions, + }); + } + return groups; + } + getAllPermissions(language: LanguageEnum = LanguageEnum.ptbr) { + let permissions: PermissionsObjectPermissionTranslated[] = []; + for (const key in PERMISSIONS) { + const permissionGroup: PermissionsObjectGroup = PERMISSIONS[key]; + const permissionGroupPermissions: PermissionsObjectPermissionTranslated[] = + Object.values(permissionGroup.permissions).map((p) => ({ + ...p, + name: p.name[language], + })); + permissions = permissions.concat(permissionGroupPermissions); + } + return permissions; + } + getPermissionsBySeqId(seqIds: number[]) { + const permissionGroupPermissions = this.getAllPermissions(); + + return permissionGroupPermissions.filter((p) => seqIds.includes(p.seqid)); + } } diff --git a/src/modules/pipelines/pipelines.controller.ts b/src/modules/pipelines/pipelines.controller.ts index 1c40836..108bb60 100644 --- a/src/modules/pipelines/pipelines.controller.ts +++ b/src/modules/pipelines/pipelines.controller.ts @@ -11,9 +11,9 @@ import { import { ApiTags } from '@nestjs/swagger'; import { PipelinesClientService } from 'src/clients/pipelines/client.service'; import { AuthenticateCondition } from 'src/authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; import { PipelinesService } from './pipelines.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @ApiTags('Pipelines') @Controller('pipelines') @@ -33,10 +33,12 @@ import { DadosferaLogger } from 'dadosfera-logs'; action = req.method; } - return user.permissions.includes(Permissions.PIPELINE[action].seqid); + return user.permissions.includes( + PERMISSIONS.PIPELINE.permissions[action].seqid, + ); }) export class PipelinesController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/roles/dto/entities.ts b/src/modules/roles/dto/entities.ts new file mode 100644 index 0000000..5652684 --- /dev/null +++ b/src/modules/roles/dto/entities.ts @@ -0,0 +1,115 @@ +import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger'; +import { PermissionDto } from 'src/modules/permissions/dto/entities'; +import { + Customer, + UserNoRolesAndCustomer, +} from 'src/modules/users/dtos/entities'; +export class RoleDto { + @ApiProperty() + id: string; + @ApiProperty() + name: string; + @ApiProperty() + description: string; + @ApiPropertyOptional({ type: () => [PermissionDto] }) + permissions: PermissionDto[]; + @ApiPropertyOptional({ type: () => [UserNoRolesAndCustomer] }) + users?: UserNoRolesAndCustomer[]; + @ApiProperty() + createdAt: string; + @ApiProperty() + updatedAt: string; + @ApiProperty() + isPublic: boolean; + @ApiProperty({ type: () => Customer }) + customer?: Customer; +} + +export class CustomerRole extends OmitType(RoleDto, ['customer']) {} +export class UserRole extends OmitType(RoleDto, ['users']) {} + +export enum TrueOrFalseEnum { + true = 'true', + false = 'false', +} +export class GetRolesByCustomerReq { + @ApiPropertyOptional() + permissionId: string[]; + @ApiPropertyOptional({ enum: TrueOrFalseEnum }) + getUsers: TrueOrFalseEnum; +} + +export class GetRolesByCustomerRes { + @ApiProperty({ type: [RoleDto] }) + roles: RoleDto[]; +} +export class CreateRoleReq { + @ApiProperty() + name: string; + @ApiProperty() + permissionIds: number[]; + @ApiPropertyOptional() + userIds: string[]; + @ApiProperty() + description: string; +} +export class CreateRoleRes { + @ApiProperty() + role: CustomerRole; +} + +export class GrantPermissionsToRoleReq { + @ApiProperty() + roleId: string; + @ApiProperty() + permissionIds: number[]; +} +export class GrantPermissionsToRoleRes { + @ApiProperty() + role: RoleDto; +} + +export class RevokePermissionsFromRoleReq { + @ApiProperty() + roleId: string; + @ApiProperty() + permissionIds: number[]; +} +export class RevokePermissionsFromRoleRes { + @ApiProperty() + role: RoleDto; +} + +export class UpdateRoleReq { + @ApiPropertyOptional() + name: string; + @ApiPropertyOptional() + description: string; +} + +export class UpdateRoleRes { + @ApiProperty() + role: RoleDto; +} + +export class SetRolePermissionsReq { + @ApiProperty() + roleId: string; + @ApiProperty() + permissionIds: number[]; +} +export class SetRolePermissionsRes { + @ApiProperty() + role: RoleDto; +} + +export class SetRoleUsersReq { + @ApiProperty() + roleId: string; + @ApiProperty() + userIds: string[]; +} +export class SetRoleUsersRes { + @ApiProperty() + role: RoleDto; +} diff --git a/src/modules/roles/roles.controller.spec.ts b/src/modules/roles/roles.controller.spec.ts new file mode 100644 index 0000000..e062694 --- /dev/null +++ b/src/modules/roles/roles.controller.spec.ts @@ -0,0 +1,46 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DucClient } from 'src/clients/duc/client.config'; +import { RolesController } from './roles.controller'; +import { RolesService } from './roles.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +const logger = { + info: (...args) => args, + error: (...args) => args, +}; +describe('RolesController', () => { + let controller: RolesController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + PermissionsModule, + ], + controllers: [RolesController], + providers: [ + RolesService, + { + provide: DadosferaLogger, + useValue: { logger }, + }, + ], + exports: [RolesService], + }).compile(); + + controller = module.get(RolesController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/roles/roles.controller.ts b/src/modules/roles/roles.controller.ts new file mode 100644 index 0000000..abcdebc --- /dev/null +++ b/src/modules/roles/roles.controller.ts @@ -0,0 +1,186 @@ +import { + Body, + Controller, + Delete, + Get, + Headers, + Inject, + Param, + Patch, + Post, + Put, + Query, + UseFilters, +} from '@nestjs/common'; +import { + ApiCreatedResponse, + ApiHeader, + ApiOkResponse, + ApiTags, +} from '@nestjs/swagger'; +import { RequestUser, User } from 'src/authentication/user.decorator'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { RolesService } from './roles.service'; +import { + GrantPermissionsToRoleReq, + GrantPermissionsToRoleRes, + CreateRoleReq, + CreateRoleRes, + GetRolesByCustomerReq, + GetRolesByCustomerRes, + RevokePermissionsFromRoleReq, + RevokePermissionsFromRoleRes, + UpdateRoleReq, + UpdateRoleRes, + RoleDto, + SetRolePermissionsReq, + SetRolePermissionsRes, + SetRoleUsersRes, + SetRoleUsersReq, +} from './dto/entities'; +import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; +import { PERMISSIONS } from 'src/authentication/permissions.enum'; +import { + Authenticated, + RequireAllPermissions, +} from 'src/authentication/authentication.decorator'; +import { LanguageEnum } from 'src/utils/languages.enum'; + +@ApiTags('Roles') +@ApiHeader({ + name: 'dadosfera-lang', + enum: LanguageEnum, + required: false, + description: 'Defaults to pt-br', +}) +@UseFilters(new GrpcToHttpExceptionFilter()) +@Authenticated() +@Controller('roles') +export class RolesController { + logger: DadosferaLogger; + + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private rolesService: RolesService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Get() + @ApiOkResponse({ type: GetRolesByCustomerRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async searchRoles( + @User() user: RequestUser, + @Query() filters: GetRolesByCustomerReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('searchRoles', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.roleSearch(filters, user); + } + + @Post() + @ApiCreatedResponse({ type: CreateRoleRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async createRole( + @User() user: RequestUser, + @Body() body: CreateRoleReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('createRole', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.createRole(body, user); + } + + @Put('permissions') + @ApiOkResponse({ type: GrantPermissionsToRoleRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async grantPermissionsToRole( + @User() user: RequestUser, + @Body() body: GrantPermissionsToRoleReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('grantPermissionsToRole', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.grantPermissionsToRole(body, user); + } + + @Delete('permissions') + @ApiOkResponse({ type: RevokePermissionsFromRoleRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async revokePermissionsFromRole( + @User() user: RequestUser, + @Body() body: RevokePermissionsFromRoleReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('revokePermissionsToRole', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.revokePermissionFromRole(body, user); + } + + @Patch('permissions') + @ApiOkResponse({ type: SetRolePermissionsRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async setRolePermissions( + @User() user: RequestUser, + @Body() body: SetRolePermissionsReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('setRolePermissions', { user }); + this.rolesService.setLanguage(language); + return this.rolesService.setRolePermissions(body, user); + } + + @Patch('users') + @ApiOkResponse({ type: SetRoleUsersRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async setRoleUsers( + @User() user: RequestUser, + @Body() body: SetRoleUsersReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('setRoleUsers', { user }); + this.rolesService.setLanguage(language); + return this.rolesService.setRoleUsers(body, user); + } + + @Get(':id') + @ApiOkResponse({ type: RoleDto }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async getRoleById( + @User() user: RequestUser, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('getRoleById', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.getRolesById(id, user); + } + + @Delete(':id') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async deleteRole( + @User() user: RequestUser, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('deleteRole', { user }); + this.rolesService.setLanguage(language); + return await this.rolesService.removeRole(id, user); + } + + @Patch(':id') + @ApiOkResponse({ type: UpdateRoleRes }) + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async updateRole( + @User() user: RequestUser, + @Param('id') id: string, + @Body() body: UpdateRoleReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('updateRole', { user }); + this.rolesService.setLanguage(language); + return this.rolesService.updateRole(id, body, user); + } +} diff --git a/src/modules/roles/roles.module.ts b/src/modules/roles/roles.module.ts new file mode 100644 index 0000000..1643b00 --- /dev/null +++ b/src/modules/roles/roles.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { ClientsModule } from '@nestjs/microservices'; +import { DucClient } from 'src/clients/duc/client.config'; +import { RolesController } from './roles.controller'; +import { RolesService } from './roles.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + PermissionsModule, + ], + controllers: [RolesController], + providers: [RolesService, DadosferaLogger], + exports: [RolesService], +}) +export class RolesModule {} diff --git a/src/modules/roles/roles.service.spec.ts b/src/modules/roles/roles.service.spec.ts new file mode 100644 index 0000000..e05341b --- /dev/null +++ b/src/modules/roles/roles.service.spec.ts @@ -0,0 +1,46 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { RolesController } from './roles.controller'; +import { RolesService } from './roles.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { DucClient } from 'src/clients/duc/client.config'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +const logger = { + info: (...args) => args, + error: (...args) => args, +}; +describe('RolesService', () => { + let service: RolesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + PermissionsModule, + ], + controllers: [RolesController], + providers: [ + RolesService, + { + provide: DadosferaLogger, + useValue: { logger }, + }, + ], + exports: [RolesService], + }).compile(); + + service = module.get(RolesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/roles/roles.service.ts b/src/modules/roles/roles.service.ts new file mode 100644 index 0000000..ed0b27b --- /dev/null +++ b/src/modules/roles/roles.service.ts @@ -0,0 +1,260 @@ +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; +import { RolesProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import { + CreateRoleReq, + GetRolesByCustomerReq, + GrantPermissionsToRoleReq, + RevokePermissionsFromRoleReq, + RoleDto, + SetRolePermissionsReq, + SetRoleUsersReq, + UpdateRoleReq, +} from './dto/entities'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { Metadata } from '@grpc/grpc-js'; +import { ClientGrpc } from '@nestjs/microservices'; +import { Permission } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; +import { lastValueFrom } from 'rxjs'; +import { PermissionsService } from '../permissions/permissions.service'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { RequestUser } from 'src/authentication/user.decorator'; + +interface GetRolesPermissionsName { + id: string; + name: string; + description: string; + permissions: Permission[]; + isPublic?: boolean; + createdAt: string; + updatedAt: string; +} +@Injectable() +export class RolesService { + logger: DadosferaLogger; + private language: LanguageEnum = LanguageEnum.ptbr; + private rolesClientService: RolesProtoService; + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + @Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc, + private permissionsService: PermissionsService, + ) { + this.logger = dadosferaLogger.logger; + } + setLanguage(lang: LanguageEnum = LanguageEnum.ptbr) { + if (Object.values(LanguageEnum).includes(lang)) this.language = lang; + this.language = lang; + } + + onModuleInit() { + this.rolesClientService = this.grpcClient.getService( + ProtoServices.RolesProtoService, + ); + } + + async getRolesById(id: string, { access_token }: RequestUser) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const role = await lastValueFrom( + this.rolesClientService.RoleFindOneById( + { + id, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async roleSearch( + filters: GetRolesByCustomerReq, + { customer_id, access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + let { permissionId: permissionIds } = filters; + if (permissionIds && !Array.isArray(permissionIds)) + permissionIds = [permissionIds]; + const { getUsers } = filters; + const isGetUsers = getUsers === 'true'; + const permissionSeqIds = + permissionIds?.map((id) => { + const seqId = Number(id); + if (isNaN(seqId)) + throw new BadRequestException(`permissionId ${id} not valid`); + return seqId; + }) || []; + const roles = await lastValueFrom( + this.rolesClientService.RoleSearch( + { + customerId: customer_id, + getUsers: isGetUsers, + permissionIds: [], + permissionSeqIds, + }, + meta, + ), + ); + const rolesTreated = this.getRolesPermissionsName(roles.roles); + return { roles: rolesTreated }; + } + + async createRole( + data: CreateRoleReq, + { customer_id, access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const { description, name, permissionIds, userIds } = data; + const role = await lastValueFrom( + this.rolesClientService.RoleCreate( + { + customerId: customer_id, + name, + permissionIds: [], + permissionSeqIds: permissionIds, + description, + userIds, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async removeRole(id: string, { access_token }: RequestUser) { + const meta = new Metadata(); + meta.add('access_token', access_token); + return lastValueFrom(this.rolesClientService.RoleRemove({ id }, meta)); + } + async grantPermissionsToRole( + { roleId, permissionIds }: GrantPermissionsToRoleReq, + { access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const role = await lastValueFrom( + this.rolesClientService.GrantPermissionsToRole( + { + permissionIds: [], + roleId, + permissionSeqIds: permissionIds, + }, + meta, + ), + ); + + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async revokePermissionFromRole( + { roleId, permissionIds }: RevokePermissionsFromRoleReq, + { access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const role = await lastValueFrom( + this.rolesClientService.RevokePermissionsFromRole( + { + permissionIds: [], + roleId, + permissionSeqIds: permissionIds, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async updateRole( + id: string, + req: UpdateRoleReq, + { access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const { description, name } = req; + const role = await lastValueFrom( + this.rolesClientService.RoleUpdate( + { + id, + description, + name, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async setRolePermissions( + req: SetRolePermissionsReq, + { access_token }: RequestUser, + ) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const { roleId: id, permissionIds } = req; + const clearPermissions = + Array.isArray(permissionIds) && permissionIds.length === 0; + const role = await lastValueFrom( + this.rolesClientService.RoleSetPermissions( + { + id, + permissionSeqIds: permissionIds, + clearPermissions, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + + async setRoleUsers(req: SetRoleUsersReq, { access_token }: RequestUser) { + const meta = new Metadata(); + meta.add('access_token', access_token); + const { roleId: id, userIds } = req; + const clearUsers = Array.isArray(userIds) && userIds.length === 0; + const role = await lastValueFrom( + this.rolesClientService.RoleSetUsers( + { + id, + userIds, + clearUsers, + }, + meta, + ), + ); + const [roleTreated] = this.getRolesPermissionsName([role.role]); + return { role: roleTreated }; + } + getRolesPermissionsName(roles: GetRolesPermissionsName[]): RoleDto[] { + const newRoles: RoleDto[] = []; + for (const role of roles) { + const allPermissions = this.permissionsService.getAllPermissions( + this.language, + ); + const newPermissions = role.permissions.map((p) => { + const permission = allPermissions.find((per) => per.seqid === p.seqid); + return { + ...p, + name: permission.name, + id: p.seqid, + }; + }); + const newRole: RoleDto = { + ...role, + permissions: newPermissions, + isPublic: role.isPublic, + }; + newRoles.push(newRole); + } + return newRoles; + } +} diff --git a/src/modules/termsOfUse/termsOfUse.controller.ts b/src/modules/termsOfUse/termsOfUse.controller.ts index a2024ae..4b88622 100644 --- a/src/modules/termsOfUse/termsOfUse.controller.ts +++ b/src/modules/termsOfUse/termsOfUse.controller.ts @@ -13,7 +13,7 @@ import { ApiTags } from '@nestjs/swagger'; import { TermsOfUseAcceptRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; import { TermsOfUseClientService } from './termsOfUse.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter'; import { RequestUser, User } from '../../authentication/user.decorator'; @@ -22,7 +22,7 @@ import { RequestUser, User } from '../../authentication/user.decorator'; @UseFilters(new GrpcToHttpExceptionFilter()) @Controller('terms-of-use') export class TermsOfUseController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) diff --git a/src/modules/termsOfUse/termsOfUse.module.ts b/src/modules/termsOfUse/termsOfUse.module.ts index e1b3eab..51c7d69 100644 --- a/src/modules/termsOfUse/termsOfUse.module.ts +++ b/src/modules/termsOfUse/termsOfUse.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ClientsModule } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { TermsOfUseController } from './termsOfUse.controller'; import { TermsOfUseClientService } from './termsOfUse.service'; diff --git a/src/modules/termsOfUse/termsOfUse.service.ts b/src/modules/termsOfUse/termsOfUse.service.ts index 76cd720..9bbe552 100644 --- a/src/modules/termsOfUse/termsOfUse.service.ts +++ b/src/modules/termsOfUse/termsOfUse.service.ts @@ -1,6 +1,6 @@ import { OnModuleInit, Inject, Injectable } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; @@ -12,7 +12,7 @@ import { @Injectable() export class TermsOfUseClientService implements OnModuleInit { - logger: any; + logger: DadosferaLogger; private termsOfUseService: TermsOfUseServiceInterface; constructor( diff --git a/src/modules/transformations/transformations.controller.ts b/src/modules/transformations/transformations.controller.ts index 71b2d21..1095aaf 100644 --- a/src/modules/transformations/transformations.controller.ts +++ b/src/modules/transformations/transformations.controller.ts @@ -13,9 +13,9 @@ import { ApiTags } from '@nestjs/swagger'; import { TransformationsClientService } from 'src/clients/transformations/client.service'; import { IIdRequest } from 'src/clients/transformations/interfaces'; import { AuthenticateCondition } from 'src/authentication/authentication.decorator'; -import { Permissions } from '../../authentication/permissions.enum'; +import { PERMISSIONS } from '../../authentication/permissions.enum'; import { TransformationsService } from './transformations.service'; -import { DadosferaLogger } from 'dadosfera-logs'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @ApiTags('Transformations') @Controller('transformations') @@ -35,10 +35,12 @@ import { DadosferaLogger } from 'dadosfera-logs'; action = req.method; } - return user.permissions.includes(Permissions.TRANSFORMATIONS[action].seqid); + return user.permissions.includes( + PERMISSIONS.TRANSFORMATIONS.permissions[action].seqid, + ); }) export class TransformationsController { - logger: any; + logger: DadosferaLogger; constructor( @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, diff --git a/src/modules/users/dtos/entities.ts b/src/modules/users/dtos/entities.ts new file mode 100644 index 0000000..5eda738 --- /dev/null +++ b/src/modules/users/dtos/entities.ts @@ -0,0 +1,171 @@ +import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger'; +import { UserRole } from 'src/modules/roles/dto/entities'; + +export class Hierarchy { + @ApiProperty() + id: string; + @ApiProperty() + name: string; +} +export class Customer { + @ApiProperty() + id: string; + @ApiProperty() + name: string; + @ApiProperty() + domain: string; + @ApiProperty() + tier: string; + @ApiProperty() + info: string; + @ApiProperty() + createdAt: string; + @ApiProperty() + updatedAt: string; +} +export class User { + @ApiProperty() + id: string; + @ApiProperty() + name: string; + @ApiProperty() + email: string; + @ApiProperty({ type: [UserRole] }) + roles: UserRole[]; + @ApiProperty() + jobTitle?: string; + @ApiProperty() + department?: string; + @ApiProperty() + hierarchy?: string; + @ApiPropertyOptional() + customer?: Customer; + @ApiProperty() + mfaStatus: string; + @ApiProperty() + createdAt: string; + @ApiProperty() + updatedAt: string; + @ApiProperty() + status: string; + @ApiPropertyOptional() + lastLogin?: string; +} +export class UserNoRoles extends OmitType(User, ['roles']) {} +export class UserNoRolesAndCustomer extends OmitType(UserNoRoles, [ + 'customer', +]) {} +export class UserByCustomer extends OmitType(User, ['customer']) {} + +export class CreateUserReq { + @ApiPropertyOptional() + name?: string; + @ApiProperty() + email: string; + @ApiPropertyOptional() + roleNames?: string[]; + @ApiPropertyOptional() + jobTitle?: string; + @ApiPropertyOptional() + department?: string; + @ApiPropertyOptional() + hierarchy?: string; +} +export class CreateUserRes { + @ApiProperty() + user: User; +} +export class BatchCreateUserReq { + @ApiProperty({ type: [CreateUserReq] }) + users: CreateUserReq[]; +} + +class ErrorUsers { + @ApiProperty() + user: CreateUserReq; + @ApiProperty() + error: string; +} + +export class BatchCreateUserRes { + @ApiProperty({ type: [User] }) + usersCreated: User[]; + @ApiProperty({ type: [ErrorUsers] }) + errorUsers: ErrorUsers[]; +} + +export class UpdateUserReq { + @ApiPropertyOptional() + name?: string; + @ApiPropertyOptional() + jobTitle?: string; + @ApiPropertyOptional() + department?: string; + @ApiPropertyOptional() + hierarchy?: string; + @ApiPropertyOptional() + roleNames?: string[]; +} + +export class UpdateUserRes { + @ApiProperty() + user: UserByCustomer; +} +export class GetAllUsersByCustomerIdRes { + @ApiProperty({ type: [UserByCustomer] }) + users: UserByCustomer[]; + @ApiProperty() + departments: string[]; + @ApiProperty() + jobTitles: string[]; +} + +export class AssignRoleToUserReq { + @ApiProperty() + userId: string; + @ApiProperty() + roleId: string; +} +export class UnassignRoleToUserReq { + @ApiProperty() + userId: string; + @ApiProperty() + roleId: string; +} +export class DeleteUserRes { + @ApiProperty() + id: string; +} + +export class SearchUserReq { + @ApiProperty() + s: string; +} + +export class SetUserRolesReq { + @ApiProperty() + userId: string; + @ApiProperty() + roleNames: string[]; +} + +export class SetUserRolesRes { + @ApiProperty() + user: UserByCustomer; +} + +export class SearchUserRes extends GetAllUsersByCustomerIdRes {} + +export class GetAllHierarchiesRes { + @ApiProperty({ type: [Hierarchy] }) + hierarchies: Hierarchy[]; +} + +export class GetAllJobTitlesRes { + @ApiProperty() + jobTitles: string[]; +} +export class GetAllDepartmentsRes { + @ApiProperty() + departments: string[]; +} diff --git a/src/modules/users/hierarchies.ts b/src/modules/users/hierarchies.ts new file mode 100644 index 0000000..9f942dd --- /dev/null +++ b/src/modules/users/hierarchies.ts @@ -0,0 +1,79 @@ +import { LanguageEnum } from 'src/utils/languages.enum'; + +interface InternalHierarchyType { + name: Record; +} +export const HIERARCHIES: Record = { + presidence: { + name: { + 'pt-br': 'Presidência', + 'en-us': 'Presidence', + }, + }, + directory: { + name: { + 'pt-br': 'Diretoria', + 'en-us': 'Directory', + }, + }, + management: { + name: { + 'pt-br': 'Gerência', + 'en-us': 'Management', + }, + }, + specialist: { + name: { + 'pt-br': 'Especialista', + 'en-us': 'Specialist', + }, + }, + supervisor: { + name: { + 'pt-br': 'Coordenador ou Supervisor', + 'en-us': 'Supervisor', + }, + }, + trainee: { + name: { + 'pt-br': 'Trainee', + 'en-us': 'Trainee', + }, + }, + 'senior-analyst': { + name: { + 'pt-br': 'Analista Sênior', + 'en-us': 'Senior Analyst', + }, + }, + 'midlevel-analyst': { + name: { + 'pt-br': 'Analista Pleno', + 'en-us': 'Mid-level Analyst', + }, + }, + 'junior-analyst': { + name: { + 'pt-br': 'Analista Júnior', + 'en-us': 'Junior Analyst', + }, + }, + intern: { + name: { + 'pt-br': 'Estagiário', + 'en-us': 'Intern', + }, + }, + 'youth-apprentice': { + name: { + 'pt-br': 'Jovem aprendiz', + 'en-us': 'Youth Apprentice', + }, + }, + other: { + name: { + 'pt-br': 'Outro', + 'en-us': 'Other', + }, + }, +}; diff --git a/src/modules/users/users.controller.spec.ts b/src/modules/users/users.controller.spec.ts new file mode 100644 index 0000000..04b4e31 --- /dev/null +++ b/src/modules/users/users.controller.spec.ts @@ -0,0 +1,47 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DucClient } from 'src/clients/duc/client.config'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { RolesModule } from '../roles/roles.module'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +const logger = { + info: (...args) => args, + error: (...args) => args, +}; +describe('UsersController', () => { + let controller: UsersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + RolesModule, + PermissionsModule, + ], + controllers: [UsersController], + providers: [ + UsersService, + { + provide: DadosferaLogger, + useValue: { logger }, + }, + ], + }).compile(); + + controller = module.get(UsersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts new file mode 100644 index 0000000..3177269 --- /dev/null +++ b/src/modules/users/users.controller.ts @@ -0,0 +1,238 @@ +import { + Body, + Controller, + Delete, + Get, + Headers, + HttpCode, + HttpStatus, + Inject, + Param, + Patch, + Post, + Put, + Req, + UseFilters, +} from '@nestjs/common'; +import { + ApiCreatedResponse, + ApiHeader, + ApiOkResponse, + ApiTags, +} from '@nestjs/swagger'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { RequireAllPermissions } from 'src/authentication/authentication.decorator'; +import { PERMISSIONS } from 'src/authentication/permissions.enum'; +import { RequestUser, User } from 'src/authentication/user.decorator'; +import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { + AssignRoleToUserReq, + BatchCreateUserReq, + BatchCreateUserRes, + CreateUserReq, + CreateUserRes, + DeleteUserRes, + GetAllDepartmentsRes, + GetAllHierarchiesRes, + GetAllJobTitlesRes, + GetAllUsersByCustomerIdRes, + SetUserRolesReq, + SetUserRolesRes, + UnassignRoleToUserReq, + UpdateUserReq, + UpdateUserRes, + UserByCustomer, +} from './dtos/entities'; +import { UsersService } from './users.service'; + +// TODO GET de hierarquias e do PATCH em usuário +@ApiTags('Users') +@Controller('users') +@ApiHeader({ + name: 'dadosfera-lang', + enum: LanguageEnum, + required: false, + description: 'Defaults to pt-br', +}) +@UseFilters(GrpcToHttpExceptionFilter) +export class UsersController { + logger: DadosferaLogger; + + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private userService: UsersService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Get() + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ + type: GetAllUsersByCustomerIdRes, + description: 'List of users of a customer', + }) + async getAllUsersByCustomerId( + @User() user: RequestUser, + @Headers('dadosfera-lang') language, + ): Promise { + this.logger.info('getAllUsersByCustomerId', { user }); + this.userService.setLanguage(language); + return await this.userService.findAllUsersByCustomerId(user.customer_id); + } + + @Get('hierarchies') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: GetAllHierarchiesRes }) + async getAllHierarchies( + @User() user: RequestUser, + @Headers('dadosfera-lang') language, + ): Promise { + this.logger.info('getAllHierarchies', { user }); + this.userService.setLanguage(language); + return await this.userService.getAllHierarchies(); + } + + @Get('departments') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: GetAllDepartmentsRes }) + async getAllDepartments( + @User() user: RequestUser, + @Headers('dadosfera-lang') language, + ): Promise { + this.logger.info('getAllHierarchies', { user }); + this.userService.setLanguage(language); + return await this.userService.getAllDepartments(user.customer_id); + } + + @Get('jobTitles') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: GetAllDepartmentsRes }) + async getAllJobTitles( + @User() user: RequestUser, + @Headers('dadosfera-lang') language, + ): Promise { + this.logger.info('getAllHierarchies', { user }); + this.userService.setLanguage(language); + return await this.userService.getAllJobTitles(user.customer_id); + } + + @Get(':id') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ + type: UserByCustomer, + }) + async findOneById( + @User() user: RequestUser, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('findOneById', { user }); + this.userService.setLanguage(language); + return await this.userService.findOneById(id); + } + + @Post() + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiCreatedResponse({ type: CreateUserRes }) + async createUser( + @User() user: RequestUser, + @Body() body: CreateUserReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('createUser', { user }); + this.userService.setLanguage(language); + return await this.userService.createUser(body); + } + + @Post('batch') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiCreatedResponse({ type: BatchCreateUserRes }) + async batchCreateUser( + @User() user: RequestUser, + @Body() body: BatchCreateUserReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('batchCreateUser', { user }); + this.userService.setLanguage(language); + return await this.userService.batchCreateUser(body); + } + + @Post(':id/resend-invite') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @HttpCode(HttpStatus.OK) + async resendInvite( + @User() user: RequestUser, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('resendInvite', { user }); + this.userService.setLanguage(language); + return this.userService.resendInvite({ id }); + } + + @Delete(':id') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: DeleteUserRes }) + async deleteUser( + @User() user: RequestUser, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('deleteUser', { user }); + this.userService.setLanguage(language); + return await this.userService.deleteUser({ id }); + } + + @Patch('roles') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: SetUserRolesRes }) + async setUserRoles( + @User() user: RequestUser, + @Body() body: SetUserRolesReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('createUser', { user }); + this.userService.setLanguage(language); + return await this.userService.setRoles(body, user.customer_id); + } + + @Put('role') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async assignRoleToUser( + @User() user: RequestUser, + @Body() body: AssignRoleToUserReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('assignRoleToUser', { user }); + this.userService.setLanguage(language); + return await this.userService.assignRoleToUser(body); + } + + @Delete('role') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + async unassignRoleToUser( + @User() user: RequestUser, + @Req() request: UnassignRoleToUserReq, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('unassignRoleToUser', { user }); + this.userService.setLanguage(language); + return await this.userService.unassignRoleToUser(request); + } + + @Patch(':id') + @RequireAllPermissions(PERMISSIONS.USERS.permissions.ADMIN) + @ApiOkResponse({ type: UpdateUserRes }) + async updateUser( + @User() user: RequestUser, + @Body() body: UpdateUserReq, + @Param('id') id: string, + @Headers('dadosfera-lang') language, + ) { + this.logger.info('updateUser', { user }); + this.userService.setLanguage(language); + return await this.userService.updateUser(body, id, user.customer_id); + } +} diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts new file mode 100644 index 0000000..4d5b88b --- /dev/null +++ b/src/modules/users/users.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ClientsModule } from '@nestjs/microservices'; +import { DucClient } from 'src/clients/duc/client.config'; +import { RolesModule } from '../roles/roles.module'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + RolesModule, + PermissionsModule, + ], + controllers: [UsersController], + providers: [UsersService, DadosferaLogger], +}) +export class UsersModule {} diff --git a/src/modules/users/users.service.spec.ts b/src/modules/users/users.service.spec.ts new file mode 100644 index 0000000..22281ce --- /dev/null +++ b/src/modules/users/users.service.spec.ts @@ -0,0 +1,47 @@ +import { ClientsModule } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { DucClient } from 'src/clients/duc/client.config'; +import { RolesModule } from '../roles/roles.module'; +import { PermissionsModule } from '../permissions/permissions.module'; + +const ducClient = new DucClient(); + +const logger = { + info: (...args) => args, + error: (...args) => args, +}; +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ClientsModule.register([ + { + name: 'DUC_PACKAGE', + ...ducClient.config(), + }, + ]), + RolesModule, + PermissionsModule, + ], + controllers: [UsersController], + providers: [ + UsersService, + { + provide: DadosferaLogger, + useValue: { logger }, + }, + ], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts new file mode 100644 index 0000000..0fe40de --- /dev/null +++ b/src/modules/users/users.service.ts @@ -0,0 +1,202 @@ +import { OnModuleInit, Inject, Injectable } from '@nestjs/common'; +import { ClientGrpc } from '@nestjs/microservices'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; + +import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; +import { UsersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import { + AssignRoleToUserRequest, + IdRequest, + UnassignRoleFromUserRequest, + UserCreateRequest, +} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; +import { lastValueFrom } from 'rxjs'; +import { + BatchCreateUserReq, + CreateUserReq, + Hierarchy, + SetUserRolesReq, + UpdateUserReq, +} from './dtos/entities'; +import { RolesService } from '../roles/roles.service'; +import { HIERARCHIES } from './hierarchies'; +import { LanguageEnum } from 'src/utils/languages.enum'; +import { UserByCustomer } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; +import { EnrichErrorCode } from 'src/utils/ErrorBuilder'; + +@Injectable() +export class UsersService implements OnModuleInit { + logger: DadosferaLogger; + language: LanguageEnum = LanguageEnum.ptbr; + + private usersClientService: UsersProtoService; + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + @Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc, + private rolesService: RolesService, + ) { + this.logger = dadosferaLogger.logger; + } + + onModuleInit() { + this.usersClientService = this.grpcClient.getService( + ProtoServices.UsersProtoService, + ); + } + + setLanguage(l: LanguageEnum = LanguageEnum.ptbr) { + if (Object.values(LanguageEnum).includes(l)) this.language = l; + this.rolesService.setLanguage(l); + } + + async findAllUsersByCustomerId(customerId: string) { + const { users } = await lastValueFrom( + this.usersClientService.UserFindAllByCustomerId({ customerId }), + ); + const usersAdj = this.adjustUsersPayload(users); + const dintinctDepartments = [...new Set(usersAdj.map((u) => u.department))] + .filter((value) => value != null) + .sort(); + const dintinctJobTitles = [...new Set(usersAdj.map((u) => u.jobTitle))] + .filter((value) => value != null) + .sort(); + return { + users: usersAdj, + jobTitles: dintinctJobTitles, + departments: dintinctDepartments, + }; + } + + async findOneById(id: string) { + const { user } = await lastValueFrom( + this.usersClientService.UserFindOneById({ id }), + ); + return { user: this.adjustUsersPayload([user])[0] }; + } + + async createUser(req: CreateUserReq) { + const { email, department, hierarchy, jobTitle, name, roleNames } = req; + const { user } = await lastValueFrom( + this.usersClientService.UserCreate({ + email, + hierarchy, + name, + roleIds: [], + department, + jobTitle, + roleNames, + }), + ); + return { user: this.adjustUsersPayload([user])[0] }; + } + + async updateUser(req: UpdateUserReq, id: string, customerId: string) { + const { department, hierarchy, jobTitle, name, roleNames } = req; + if (roleNames) { + await this.setRoles({ roleNames, userId: id }, customerId); + } + const { user } = await lastValueFrom( + this.usersClientService.UserUpdate({ + name, + customerId, + id, + department, + hierarchy, + jobTitle, + }), + ); + return { user: this.adjustUsersPayload([user])[0] }; + } + + async setRoles(body: SetUserRolesReq, customerId: string) { + const { roleNames, userId } = body; + const clearRoles = roleNames && roleNames.length === 0; + const { user } = await lastValueFrom( + this.usersClientService.UserSetRoles({ + userId, + roleIds: [], + roleNames, + clearRoles, + customerId, + }), + ); + + return { user: this.adjustUsersPayload([user])[0] }; + } + + async batchCreateUser({ users }: BatchCreateUserReq) { + const usersToCreate: UserCreateRequest[] = users.map( + ({ roleNames, email, department, hierarchy, jobTitle, name }) => ({ + roleNames, + email, + department, + hierarchy, + jobTitle, + name, + roleIds: [], + }), + ); + const { errorUsers, usersCreated } = await lastValueFrom( + this.usersClientService.UserBatchCreate({ + users: usersToCreate, + }), + ); + + return { + usersCreated: this.adjustUsersPayload(usersCreated), + // mapping error code to message + errorUsers: errorUsers.map(({ user, error }) => ({ + user, + error: EnrichErrorCode(error).message, + })), + }; + } + + async resendInvite(body: IdRequest) { + return lastValueFrom(this.usersClientService.UserResendInvite(body)); + } + + async assignRoleToUser(body: AssignRoleToUserRequest) { + return lastValueFrom(this.usersClientService.AssignRoleToUser(body)); + } + + async unassignRoleToUser(body: UnassignRoleFromUserRequest) { + return lastValueFrom(this.usersClientService.UnassignRoleFromUser(body)); + } + + async deleteUser(id: IdRequest) { + return lastValueFrom(this.usersClientService.UserRemove(id)); + } + + getAllHierarchies(): { hierarchies: Hierarchy[] } { + const hierarchies = Object.keys(HIERARCHIES).map((key) => ({ + id: key, + name: HIERARCHIES[key]?.name[this.language] || key, + })); + return { hierarchies }; + } + + async getAllDepartments(customerId?: string) { + return lastValueFrom( + this.usersClientService.FindDistinctDepartments({ customerId }), + ); + } + + async getAllJobTitles(customerId?: string) { + return lastValueFrom( + this.usersClientService.FindDistinctJobTitles({ customerId }), + ); + } + + private adjustUsersPayload(users: UserByCustomer[]) { + return users.map((u) => { + if (!u) return undefined; + return { + ...u, + roles: this.rolesService.getRolesPermissionsName(u.roles), + hierarchy: HIERARCHIES[u.hierarchy]?.name[this.language] || u.hierarchy, + }; + }); + } +} diff --git a/src/utils/ErrorBuilder.ts b/src/utils/ErrorBuilder.ts index 50d8640..5bf9304 100644 --- a/src/utils/ErrorBuilder.ts +++ b/src/utils/ErrorBuilder.ts @@ -128,6 +128,74 @@ export function EnrichErrorCode(code: string) { code, }; + case ErrorCodes.ROLE.ALREADY_EXIST: + return { + statusCode: HttpStatus.BAD_REQUEST, + error: 'Grupo já existe', + message: 'Não é possível criar um grupo com esse nome', + code, + }; + case ErrorCodes.ROLE.NOT_FOUND: + case ErrorCodes.ROLE.FROM_ANOTHER_CUSTOMER: + return { + statusCode: HttpStatus.NOT_FOUND, + error: 'Grupo não encontrado', + message: 'Grupo solicitado não encontrado', + code: ErrorCodes.ROLE.NOT_FOUND, + }; + case ErrorCodes.ROLE.CANNOT_EDIT_PUBLIC_ROLE: + return { + statusCode: HttpStatus.FORBIDDEN, + error: 'Grupo público', + message: 'Você não tem permissão para editar esse grupo', + code, + }; + case ErrorCodes.USER.NOT_FOUND: + return { + statusCode: HttpStatus.NOT_FOUND, + error: 'Usuário não encontrado', + message: 'Usuário solicitado não encontrado', + code, + }; + case ErrorCodes.USER.ALREADY_EXISTS: + return { + statusCode: HttpStatus.NOT_FOUND, + error: 'Usuário encontrado', + message: 'Nome de usuário já existente', + code, + }; + case ErrorCodes.USER.INVALID_ID: + return { + statusCode: HttpStatus.NOT_FOUND, + error: 'Id inválido', + message: 'Id encontra-se num formato inválido', + code, + }; + case ErrorCodes.CONNECTION_TEST.ERROR: + return { + statusCode: HttpStatus.BAD_REQUEST, + error: 'Erro ao realizar o teste', + message: 'Verifique os dados de conexão e tente novamente', + code, + }; + + case ErrorCodes.CONNECTION_TEST.EXPIRED: + return { + statusCode: HttpStatus.NOT_FOUND, + error: 'Teste de conexão expirado', + message: 'Favor realizar novo teste', + code, + }; + + case ErrorCodes.CONNECTION_TEST.TIMEOUT: + return { + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + error: 'Tempo limite excedido', + message: + 'O teste excedeu o tempo limite. Verifique os dados de conexão e tente novamente', + code, + }; + case ErrorCodes.INTERNAL: case ErrorCodes.UNKNOWN: default: @@ -137,6 +205,7 @@ export function EnrichErrorCode(code: string) { message: 'Erro desconhecido. Tente novamente ou entre em contato com o suporte', code: ErrorCodes.UNKNOWN, + details: code, }; } } @@ -146,16 +215,22 @@ const logger = new Logger(); export default class ErrorBuilder extends HttpException { code: string; - constructor(code: string | RpcException) { + constructor(code: string | RpcException = '') { if (typeof code !== 'string') { - logger.log(code.stack); - code = (code as any).details as string; + logger.log(code?.stack); + code = (code as any)?.details as string; } else { logger.log(code); } - const { statusCode, message, error, code: rCode } = EnrichErrorCode(code); - super({ statusCode, message, error, code: rCode }, statusCode); + const { + statusCode, + message, + error, + code: rCode, + details, + } = EnrichErrorCode(code); + super({ statusCode, message, error, code: rCode, details }, statusCode); this.code = code; } } diff --git a/src/utils/errorCodes.ts b/src/utils/errorCodes.ts index 0293678..992243f 100644 --- a/src/utils/errorCodes.ts +++ b/src/utils/errorCodes.ts @@ -13,18 +13,48 @@ export const AUTH = { RESET_PASSWORD_CODE_INVALID: 'AUTH.RESET_PASSWORD_CODE_INVALID', WEAK_NEW_PASSWORD: 'AUTH.WEAK_NEW_PASSWORD', }; +export const ROLE = { + ALREADY_EXIST: 'ROLE.ALREADY_EXISTS', + NOT_FOUND: 'ROLE.NOT_FOUND', + FROM_ANOTHER_CUSTOMER: 'ROLE.FROM_ANOTHER_CUSTOMER', + CANNOT_EDIT_PUBLIC_ROLE: 'ROLE.CANNOT_EDIT_PUBLIC_ROLE', +}; +export const USER = { + INVALID_ID: 'USER.INVALID_ID', + NOT_FOUND: 'USER.NOT_FOUND', + ALREADY_EXISTS: 'USER.ALREADY_EXISTS', + ROLE_ALREADY_ASSIGNED: 'USER.ROLE_ALREADY_ASSIGNED', + ROLE_NOT_ASSIGNED: 'USER.ROLE_NOT_ASSIGNED', +}; + +export const CUSTOMER = { + INVALID_ID: 'CUSTOMER.INVALID_ID', + NOT_FOUND: 'CUSTOMER.NOT_FOUND', + ALREADY_EXISTS: 'CUSTOMER.ALREADY_EXISTS', +}; const TERMS_OF_USE = { UP_TO_DATE: 'TERMS_OF_USE.UP_TO_DATE', REQUIRED: 'TERMS_OF_USE.REQUIRED', }; +const CONNECTION_TEST = { + ERROR: 'CONNECTION_TEST.ERROR', + EXPIRED: 'CONNECTION_TEST.EXPIRED', + TIMEOUT: 'CONNECTION_TEST.TIMEOUT', + UNKNOWN: 'CONNECTION_TEST.UNKNOWN', +}; + const ErrorCodes = { UNKNOWN: 'UNKNOWN', RATE_LIMIT: 'RATE_LIMIT', INTERNAL: 'INTERNAL', AUTH, + ROLE, TERMS_OF_USE, + USER, + CUSTOMER, + CONNECTION_TEST, }; export default ErrorCodes; diff --git a/src/utils/languages.enum.ts b/src/utils/languages.enum.ts new file mode 100644 index 0000000..1fb3bc1 --- /dev/null +++ b/src/utils/languages.enum.ts @@ -0,0 +1,4 @@ +export enum LanguageEnum { + ptbr = 'pt-br', + enus = 'en-us', +}