mirror of
https://github.com/dadosfera/maestro.git
synced 2026-09-01 12:18:15 +00:00
Compare commits
491
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c4ed179b | ||
|
|
e55a1cb657 | ||
|
|
3850d4b8ae | ||
|
|
2a6677e4d9 | ||
|
|
74e62c001d | ||
|
|
8237160c6d | ||
|
|
41bf9d4856 | ||
|
|
44dcf1b281 | ||
|
|
104787bce8 | ||
|
|
38a330e578 | ||
|
|
7d0b8755c9 | ||
|
|
7aadf4d9d1 | ||
|
|
af8fa72377 | ||
|
|
c51f262474 | ||
|
|
54d19a78ab | ||
|
|
66f33a2246 | ||
|
|
55d9441d10 | ||
|
|
8b7342652e | ||
|
|
118c2653a5 | ||
|
|
f7d0128d99 | ||
|
|
12cc555491 | ||
|
|
cf5e36db00 | ||
|
|
7c0b87ffc3 | ||
|
|
beb086ad3c | ||
|
|
8c12291096 | ||
|
|
56a063b644 | ||
|
|
3a6054c1c6 | ||
|
|
3a696ecb0e | ||
|
|
1166073b1c | ||
|
|
3026cd0748 | ||
|
|
b48d3ae667 | ||
|
|
5868f21f22 | ||
|
|
873f7ea314 | ||
|
|
2b01b340e4 | ||
|
|
5888088486 | ||
|
|
9e2de28032 | ||
|
|
db2ea09d25 | ||
|
|
8c6b514b7b | ||
|
|
ad03d663bb | ||
|
|
48d4cbc41a | ||
|
|
ae81816aa7 | ||
|
|
ccdff58056 | ||
|
|
c10f85950a | ||
|
|
7d69220461 | ||
|
|
e316bd1a7b | ||
|
|
fd47a746af | ||
|
|
5038c7845f | ||
|
|
9876eab521 | ||
|
|
99a52c3ff2 | ||
|
|
0119cee3c6 | ||
|
|
c7e850cc79 | ||
|
|
5da47e5438 | ||
|
|
b0f9f5c77e | ||
|
|
35f35ddeb3 | ||
|
|
90155a8811 | ||
|
|
ae8baca694 | ||
|
|
82b8751f65 | ||
|
|
78ee64fc45 | ||
|
|
2f30837fed | ||
|
|
f46b1b62db | ||
|
|
8b6cd8a88e | ||
|
|
0c333b476a | ||
|
|
1aa9371245 | ||
|
|
14fcf4a0a1 | ||
|
|
24f824bcd1 | ||
|
|
fcdd0bf571 | ||
|
|
c416a2cb97 | ||
|
|
d03021fc9d | ||
|
|
574cf48726 | ||
|
|
7cef404acf | ||
|
|
3c32c3c7d4 | ||
|
|
d44531b963 | ||
|
|
36c80b480e | ||
|
|
eeef97679c | ||
|
|
c789dae3f6 | ||
|
|
4b20388d45 | ||
|
|
33fd2bd35f | ||
|
|
654c837d3f | ||
|
|
416b58080d | ||
|
|
216defb76f | ||
|
|
0970c748e0 | ||
|
|
aabd741cad | ||
|
|
950c6414b6 | ||
|
|
d1149a4d52 | ||
|
|
4c6f4bfd84 | ||
|
|
9caf3dc67f | ||
|
|
635c4da48c | ||
|
|
44bb205483 | ||
|
|
edb4622684 | ||
|
|
abb46d78da | ||
|
|
04fdc0368a | ||
|
|
50d76622c5 | ||
|
|
238f56cb76 | ||
|
|
e721d6f5f5 | ||
|
|
44df13e820 | ||
|
|
1fd0f79dd5 | ||
|
|
1b92371737 | ||
|
|
bf5b3484a1 | ||
|
|
eb1e248d4b | ||
|
|
0827414051 | ||
|
|
081986607b | ||
|
|
189080199a | ||
|
|
55281e3965 | ||
|
|
db8a6175e8 | ||
|
|
7b0d4dd5bc | ||
|
|
6fd277ef3a | ||
|
|
7b25bfdb69 | ||
|
|
2abc19c589 | ||
|
|
faf6b1df75 | ||
|
|
422be4a422 | ||
|
|
da136e0ef0 | ||
|
|
737cc9b8ea | ||
|
|
23f6b179fd | ||
|
|
03e451c21d | ||
|
|
be061a0fb9 | ||
|
|
c83f6a68d1 | ||
|
|
2646e3e4f6 | ||
|
|
06424d6bcf | ||
|
|
45b82515fc | ||
|
|
a9cc951e34 | ||
|
|
93ccf506fb | ||
|
|
706d0ccfa0 | ||
|
|
c2eec4e95c | ||
|
|
ac3132b6eb | ||
|
|
ee8c5e7a93 | ||
|
|
a2c1f2da6f | ||
|
|
7ecb50315f | ||
|
|
84b3370826 | ||
|
|
aa9e0536a7 | ||
|
|
544d13fac5 | ||
|
|
a4ff07a285 | ||
|
|
28578d3aa8 | ||
|
|
b8ea729a33 | ||
|
|
b383070b68 | ||
|
|
87882b5afc | ||
|
|
6a14c2b459 | ||
|
|
f0bfb6ba8e | ||
|
|
99288fa805 | ||
|
|
35cc0094b3 | ||
|
|
98262bcba9 | ||
|
|
a2c9a462e4 | ||
|
|
c5c089e5d5 | ||
|
|
de06512249 | ||
|
|
9058b80383 | ||
|
|
be64c582e1 | ||
|
|
d6b3e02890 | ||
|
|
d06910515b | ||
|
|
d1b328d481 | ||
|
|
fbc0d2eede | ||
|
|
945955a71c | ||
|
|
9fcbeda470 | ||
|
|
8043635b1c | ||
|
|
cdba41dffa | ||
|
|
5020e8913e | ||
|
|
89369270b9 | ||
|
|
52f17725af | ||
|
|
6da213aebc | ||
|
|
a353a1b45c | ||
|
|
b36ff624b5 | ||
|
|
03c09a525b | ||
|
|
9ea610a405 | ||
|
|
cd97bcbac0 | ||
|
|
52bf2bdef3 | ||
|
|
161d1fa05d | ||
|
|
642bf462ad | ||
|
|
6d61d74d0c | ||
|
|
2bc060b13d | ||
|
|
28efdf95d9 | ||
|
|
1ef9fd4298 | ||
|
|
5c24ec5560 | ||
|
|
962e094a65 | ||
|
|
131b166d8d | ||
|
|
5419f3690c | ||
|
|
6f3eb7965b | ||
|
|
909f0d4a4e | ||
|
|
8c8fdf5f88 | ||
|
|
e9582a51c2 | ||
|
|
0590c20af8 | ||
|
|
05eefe866b | ||
|
|
fa023756c7 | ||
|
|
7f967e5e75 | ||
|
|
2a0b17e45a | ||
|
|
b1f6d765b2 | ||
|
|
3c9ed08515 | ||
|
|
c7ca8297c9 | ||
|
|
d269e9efe4 | ||
|
|
61cc238891 | ||
|
|
b5df916511 | ||
|
|
95da106fb8 | ||
|
|
5730402312 | ||
|
|
c2ea8b69da | ||
|
|
14081bb9ca | ||
|
|
82d9024f24 | ||
|
|
53a6d3957a | ||
|
|
7dab555009 | ||
|
|
27dacf1257 | ||
|
|
f71bb523ff | ||
|
|
db55a8abde | ||
|
|
8f40b159d6 | ||
|
|
5c6f36a760 | ||
|
|
db4a2d9d2e | ||
|
|
9a1b3e0bf4 | ||
|
|
67d9b3a06e | ||
|
|
1150d21763 | ||
|
|
b38e9f26c3 | ||
|
|
5a23923e53 | ||
|
|
3db7e99d9e | ||
|
|
c85cd37f3a | ||
|
|
190a63f0eb | ||
|
|
0fc8c427a2 | ||
|
|
59c3ce7ed9 | ||
|
|
c5c428d3d4 | ||
|
|
cda7f9a9b5 | ||
|
|
bb3b71d5f0 | ||
|
|
e47459eb9a | ||
|
|
a2de729e0e | ||
|
|
e2cb02e51a | ||
|
|
8a0c91eca5 | ||
|
|
15302d80bc | ||
|
|
5aa63154c5 | ||
|
|
48af897e79 | ||
|
|
9eb61a0ac9 | ||
|
|
4a9e5b0ffc | ||
|
|
23fe522cc1 | ||
|
|
ea8dfa2e12 | ||
|
|
9477223bec | ||
|
|
519070bd82 | ||
|
|
99fc7e8a96 | ||
|
|
9fedd4bb80 | ||
|
|
ff5e735121 | ||
|
|
c450d16445 | ||
|
|
3acda53e8a | ||
|
|
a0f21b763a | ||
|
|
c83d8ab1b2 | ||
|
|
610d08fa11 | ||
|
|
21f68b466d | ||
|
|
18b5dc980c | ||
|
|
6fdd73c58c | ||
|
|
eaddca7eb3 | ||
|
|
1c0377a4fa | ||
|
|
64816e5578 | ||
|
|
f9064417ce | ||
|
|
0b8bf9a535 | ||
|
|
98e865c48a | ||
|
|
2c207f068c | ||
|
|
6f1e5d08c9 | ||
|
|
b611c1eb9d | ||
|
|
d7062efc60 | ||
|
|
4a3dd98977 | ||
|
|
81d93ff103 | ||
|
|
ea7ec52e1f | ||
|
|
be228b7425 | ||
|
|
90edc6552a | ||
|
|
ed96609011 | ||
|
|
d55563f62f | ||
|
|
ba9575c53e | ||
|
|
d88b154d43 | ||
|
|
06f4260b61 | ||
|
|
64717c8d92 | ||
|
|
e6e66c77e1 | ||
|
|
131e82b4c5 | ||
|
|
32d37ecadf | ||
|
|
4d667acb86 | ||
|
|
9d417ff61b | ||
|
|
b01c4c6c25 | ||
|
|
449a1abb63 | ||
|
|
04ca17a69d | ||
|
|
34a0eb40a7 | ||
|
|
2df69fba84 | ||
|
|
dcbcc5ff36 | ||
|
|
703b5f36b3 | ||
|
|
54d10cb873 | ||
|
|
569c3cf573 | ||
|
|
c420ef62ed | ||
|
|
966ba00fec | ||
|
|
0aadf7fc40 | ||
|
|
3e3268cb26 | ||
|
|
09bd623b62 | ||
|
|
4bf9a5831a | ||
|
|
264077289b | ||
|
|
14c686cff4 | ||
|
|
0e57667419 | ||
|
|
ac7dc4905e | ||
|
|
94282718ad | ||
|
|
7c228c5c53 | ||
|
|
14e0f3d2d3 | ||
|
|
85ef0d7c74 | ||
|
|
e00b6481d0 | ||
|
|
8906c73d84 | ||
|
|
3bc37db891 | ||
|
|
3c877b8b8d | ||
|
|
fe44664970 | ||
|
|
0503732c1e | ||
|
|
fa0855d631 | ||
|
|
38aee30f2a | ||
|
|
19f5c947bd | ||
|
|
44aa0e6010 | ||
|
|
cf4e7da1d8 | ||
|
|
8bd7d54aa7 | ||
|
|
a00a66e0d0 | ||
|
|
f4c5d03269 | ||
|
|
060ba7da8a | ||
|
|
5ec2c01b20 | ||
|
|
4a7e660a49 | ||
|
|
abc6258492 | ||
|
|
7ee1215da2 | ||
|
|
cd5886337b | ||
|
|
e6b0034805 | ||
|
|
b9c1f784d3 | ||
|
|
5fb8dae312 | ||
|
|
5072a54ef6 | ||
|
|
718f5cb12a | ||
|
|
4c41dd8029 | ||
|
|
fe8a0b88b0 | ||
|
|
3edf5c2416 | ||
|
|
cd67ca61cd | ||
|
|
b9439685f3 | ||
|
|
04edce0c56 | ||
|
|
f3e00a15c8 | ||
|
|
0422c7a369 | ||
|
|
f22d9370db | ||
|
|
ef098dd74d | ||
|
|
2541960b44 | ||
|
|
2dc965ed90 | ||
|
|
a9ee74724f | ||
|
|
b2f1ce669d | ||
|
|
602d81e51f | ||
|
|
bbdb023dde | ||
|
|
9cb4cd7093 | ||
|
|
efc5870d87 | ||
|
|
c3d33f8230 | ||
|
|
0654e8fa04 | ||
|
|
28ab892208 | ||
|
|
d1522eb0ea | ||
|
|
6be9e06b22 | ||
|
|
7af261d56c | ||
|
|
8fa924fdfb | ||
|
|
4b4bdfea61 | ||
|
|
d1064c852b | ||
|
|
63338f5250 | ||
|
|
5e1fb41930 | ||
|
|
a807da2414 | ||
|
|
85f14fcf4b | ||
|
|
d95898ce55 | ||
|
|
33075b0f85 | ||
|
|
ca75f8f6e7 | ||
|
|
af12a39959 | ||
|
|
f77476b9ae | ||
|
|
fb0c659cd1 | ||
|
|
aea6815de7 | ||
|
|
0103f100f5 | ||
|
|
66b6be43b7 | ||
|
|
c029e286b5 | ||
|
|
a8f94a2d53 | ||
|
|
aadb3fcdee | ||
|
|
a26e88d2b6 | ||
|
|
b20f67e8b8 | ||
|
|
4a8a8eb463 | ||
|
|
d52f8b78c8 | ||
|
|
bfbe7cce87 | ||
|
|
1ab8c1dd10 | ||
|
|
246de83142 | ||
|
|
dfa6222538 | ||
|
|
fc7a9c7909 | ||
|
|
932616a578 | ||
|
|
b64619ede1 | ||
|
|
4a08e5f560 | ||
|
|
4ffd0916c3 | ||
|
|
92041f453c | ||
|
|
f2f09cc9c7 | ||
|
|
d486231fd6 | ||
|
|
526a3bddcd | ||
|
|
45108d3019 | ||
|
|
ac3e26b851 | ||
|
|
7fbc919716 | ||
|
|
5b1eb19511 | ||
|
|
b714f3cf36 | ||
|
|
48c1e29168 | ||
|
|
04ea1d0f04 | ||
|
|
0de5e1d5d1 | ||
|
|
b8c41fad1b | ||
|
|
ef9ec5ad42 | ||
|
|
65e71f21a0 | ||
|
|
b921393ba1 | ||
|
|
18aa7d8674 | ||
|
|
f2dd871de2 | ||
|
|
cf3fa88196 | ||
|
|
131810533c | ||
|
|
ea06f3f571 | ||
|
|
81921a7eb2 | ||
|
|
e9fdd0285b | ||
|
|
167470adba | ||
|
|
fbb9a59214 | ||
|
|
ef6d8594a3 | ||
|
|
ac73c0b046 | ||
|
|
5a30bf08b2 | ||
|
|
62b4eb709e | ||
|
|
b37a18d1da | ||
|
|
379bac704f | ||
|
|
9178530a21 | ||
|
|
667d3f874d | ||
|
|
475af224bf | ||
|
|
11f1df5537 | ||
|
|
e3b02571c8 | ||
|
|
979a12aa7f | ||
|
|
7646d7a3c5 | ||
|
|
8f0e851a88 | ||
|
|
779418f471 | ||
|
|
6e6d03f302 | ||
|
|
85f41521ed | ||
|
|
e2bb119745 | ||
|
|
3d5ce59b52 | ||
|
|
1c63271f64 | ||
|
|
31a51c20aa | ||
|
|
62730eb969 | ||
|
|
5a2c760bab | ||
|
|
28b4f3ac4b | ||
|
|
43d181a429 | ||
|
|
a72fe1b60d | ||
|
|
b52a1227b1 | ||
|
|
31478ed61d | ||
|
|
79b1ba910e | ||
|
|
7fd929915b | ||
|
|
e4b27c5f76 | ||
|
|
92e449d6d3 | ||
|
|
5073b2e188 | ||
|
|
8a11eda24d | ||
|
|
9d732d4a48 | ||
|
|
5fa10a939b | ||
|
|
4be3ffe3a2 | ||
|
|
8af9c638ef | ||
|
|
97d6f914cc | ||
|
|
9ad2e3770e | ||
|
|
ea2cc8809f | ||
|
|
410e190e7c | ||
|
|
c1b213c34c | ||
|
|
a6c58eb82d | ||
|
|
b756cb5a1e | ||
|
|
46cafc2d50 | ||
|
|
a087775e3a | ||
|
|
69305610ff | ||
|
|
69008a9ffb | ||
|
|
687b4e40e1 | ||
|
|
d56544568b | ||
|
|
cad1e4b3de | ||
|
|
b311a84bde | ||
|
|
28280dca8d | ||
|
|
63fbfa6b52 | ||
|
|
2b03c4e4a9 | ||
|
|
18c3c9c58c | ||
|
|
450c82446e | ||
|
|
beb27a0f60 | ||
|
|
166c45728f | ||
|
|
d0f5da0270 | ||
|
|
dade8d59e5 | ||
|
|
fe261667f8 | ||
|
|
722bb1ec7c | ||
|
|
a371db355b | ||
|
|
1ff135c0d7 | ||
|
|
0360711ab4 | ||
|
|
b6ec2a83b2 | ||
|
|
812dc0c735 | ||
|
|
11715dd528 | ||
|
|
be476c1a6d | ||
|
|
a8414f780a | ||
|
|
1da61a4c13 | ||
|
|
7e8f5c49b5 | ||
|
|
96a485afd1 | ||
|
|
e3c203d673 | ||
|
|
d7da68241e | ||
|
|
5d924f8a92 | ||
|
|
f9b4ddd070 | ||
|
|
c0e0732b7e | ||
|
|
37ea7c2f4d | ||
|
|
d42584fcfc | ||
|
|
a28781423c | ||
|
|
8c638610c7 | ||
|
|
55ff485fd0 | ||
|
|
4877fab945 | ||
|
|
e724ef5ccc | ||
|
|
9dc96416e8 | ||
|
|
49663c4115 | ||
|
|
c16664c7f9 | ||
|
|
7245942707 | ||
|
|
c29cf5aee1 | ||
|
|
d96658c9cd | ||
|
|
cbd736b87d | ||
|
|
e4e6d95f8a | ||
|
|
a8e13c01d2 | ||
|
|
cdf2ce3cc2 | ||
|
|
c3877657ef |
+1
-1
@@ -8,7 +8,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:prettier/recommended',
|
'prettier',
|
||||||
],
|
],
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- beta
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
environment:
|
environment:
|
||||||
description: 'Deployment environment'
|
description: "Deployment environment"
|
||||||
required: true
|
required: true
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- dev
|
|
||||||
- stg
|
- stg
|
||||||
- prd
|
- prd
|
||||||
|
push_to_dockerhub:
|
||||||
|
description: "Push image to Dockerhub?"
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
extract_environment:
|
extract_environment:
|
||||||
@@ -26,9 +31,11 @@ jobs:
|
|||||||
- name: Extract Environment
|
- name: Extract Environment
|
||||||
run: |
|
run: |
|
||||||
if [ ${EVENT} == "workflow_dispatch" ]; then
|
if [ ${EVENT} == "workflow_dispatch" ]; then
|
||||||
echo "##[set-output name=environment;]$(echo ${DEPLOY_ENV})"
|
echo "environment=${DEPLOY_ENV}" >> $GITHUB_OUTPUT
|
||||||
elif [ ${GITHUB_REF} == "refs/heads/main" ]; then
|
elif [ ${GITHUB_REF} == "refs/heads/main" ]; then
|
||||||
echo "##[set-output name=environment;]$(echo "prd")"
|
echo "environment=prd" >> $GITHUB_OUTPUT
|
||||||
|
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
|
||||||
|
echo "environment=stg" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
id: extract_environment
|
id: extract_environment
|
||||||
|
|
||||||
@@ -39,28 +46,35 @@ jobs:
|
|||||||
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }}
|
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- if: github.event_name != 'workflow_dispatch'
|
- if: github.event_name != 'workflow_dispatch'
|
||||||
name: Semantic Release
|
name: Semantic Release
|
||||||
uses: cycjimmy/semantic-release-action@v2
|
uses: cycjimmy/semantic-release-action@v3
|
||||||
id: semantic
|
id: semantic
|
||||||
with:
|
with:
|
||||||
semantic_version: 16
|
|
||||||
extra_plugins: |
|
extra_plugins: |
|
||||||
conventional-changelog-eslint
|
conventional-changelog-eslint@4.0.0
|
||||||
branches: |
|
branches: |
|
||||||
[
|
[
|
||||||
'main'
|
'main',
|
||||||
|
{
|
||||||
|
name: 'alpha',
|
||||||
|
prerelease: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'beta',
|
||||||
|
prerelease: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
deploy:
|
build_ecr_image:
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
|
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
|
||||||
needs: [extract_environment, semantic_release]
|
needs: [extract_environment, semantic_release]
|
||||||
runs-on:
|
runs-on:
|
||||||
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
|
[self-hosted, "prd"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Printing stats
|
- name: Printing stats
|
||||||
@@ -71,16 +85,12 @@ jobs:
|
|||||||
run: echo ${GITHUB_REF#refs/heads/}
|
run: echo ${GITHUB_REF#refs/heads/}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Update Pip
|
- name: Update Pip
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
- name: Install Docker Compose
|
|
||||||
run: |
|
|
||||||
python3 -m pip install docker-compose --upgrade
|
|
||||||
|
|
||||||
- name: Install AWS CLI
|
- name: Install AWS CLI
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install awscli --upgrade
|
python3 -m pip install awscli --upgrade
|
||||||
@@ -90,14 +100,14 @@ jobs:
|
|||||||
python3 -m pip install awsebcli --upgrade
|
python3 -m pip install awsebcli --upgrade
|
||||||
|
|
||||||
- name: Configure AWS Region
|
- name: Configure AWS Region
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
id: aws
|
id: aws
|
||||||
with:
|
with:
|
||||||
aws-region: us-east-1
|
aws-region: us-east-1
|
||||||
|
|
||||||
- name: Login to AWS ECR
|
- name: Login to AWS ECR
|
||||||
id: login_ecr
|
id: login_ecr
|
||||||
uses: aws-actions/amazon-ecr-login@v1
|
uses: aws-actions/amazon-ecr-login@v2
|
||||||
|
|
||||||
- name: Build, Tag, and Push Image to AWS ECR
|
- name: Build, Tag, and Push Image to AWS ECR
|
||||||
env:
|
env:
|
||||||
@@ -105,102 +115,103 @@ jobs:
|
|||||||
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||||
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||||
run: |
|
run: |
|
||||||
docker-compose -f build.docker-compose.yml build
|
docker compose -f build.docker-compose.yml build
|
||||||
docker-compose -f build.docker-compose.yml push
|
docker compose -f build.docker-compose.yml push
|
||||||
|
|
||||||
- name: Create ZIP file to Deploy AWS Beanstalk
|
- name: Login to Docker Hub
|
||||||
|
if: ${{inputs.push_to_dockerhub}}
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: dadosfera
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build, Tag, and Push Image to Dockerhub
|
||||||
|
if: ${{inputs.push_to_dockerhub}}
|
||||||
env:
|
env:
|
||||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||||
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||||
run: |
|
run: |
|
||||||
sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
|
docker compose -f build.docker-compose.dockerhub.yml build
|
||||||
sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
|
docker compose -f build.docker-compose.dockerhub.yml push
|
||||||
sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
|
|
||||||
zip deploy.zip docker-compose.yml -r .ebextensions
|
|
||||||
|
|
||||||
- name: Deploy AWS Beanstalk
|
# - name: Create ZIP file to Deploy AWS Beanstalk
|
||||||
env:
|
# env:
|
||||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
# ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
AWS_REGION: us-east-1
|
# IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||||
APP_NAME: ${{ github.event.repository.name }}
|
# ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||||
run: |
|
# NODE_EXPORTER_URL: ${{needs.extract_environment.outputs.environment == 'prd' && '611330257153.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest' || '468720548566.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest'}}
|
||||||
eb use $APP_NAME-$ENV
|
# run: |
|
||||||
echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
|
# sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
|
||||||
eb deploy
|
# sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
|
||||||
|
# sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
|
||||||
|
# sed -i -e "s/\${NODE_EXPORTER_URL}/$NODE_EXPORTER_URL/g" docker-compose.yml
|
||||||
|
# zip deploy.zip docker-compose.yml -r .ebextensions
|
||||||
|
|
||||||
|
# - name: Deploy AWS Beanstalk
|
||||||
|
# env:
|
||||||
|
# ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
|
# AWS_REGION: us-east-1
|
||||||
|
# APP_NAME: ${{ github.event.repository.name }}
|
||||||
|
# run: |
|
||||||
|
# eb use $APP_NAME-$ENV
|
||||||
|
# echo -e "deploy:\n artifact: deploy.zip" >> .elasticbeanstalk/config.yml
|
||||||
|
# eb deploy
|
||||||
|
|
||||||
- name: Remove Docker's Trash
|
- name: Remove Docker's Trash
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
docker system prune --volumes -a -f
|
docker system prune --volumes -a -f
|
||||||
docker system df
|
docker system df
|
||||||
api_docs:
|
|
||||||
needs: [extract_environment, semantic_release, deploy]
|
helmfile-deploy:
|
||||||
runs-on:
|
needs: [extract_environment, semantic_release, build_ecr_image]
|
||||||
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
|
runs-on: [self-hosted, "prd-azure"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Extract Docs PageId
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v1
|
||||||
|
with:
|
||||||
|
version: 'v3.9.0'
|
||||||
|
|
||||||
|
- name: Install Azure ClI
|
||||||
|
run: |
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
|
||||||
|
|
||||||
|
- uses: azure/login@v2
|
||||||
|
with:
|
||||||
|
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.8'
|
||||||
|
|
||||||
|
- name: Install Helmfile
|
||||||
|
run: |
|
||||||
|
wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz
|
||||||
|
tar -xzf helmfile_0.148.0_linux_amd64.tar.gz
|
||||||
|
mv helmfile /usr/local/bin/
|
||||||
|
helmfile --version
|
||||||
|
|
||||||
|
- name: Install Helm Diff Plugin
|
||||||
|
run: helm plugin install https://github.com/databus23/helm-diff || true
|
||||||
|
|
||||||
|
- name: Setup kubectl
|
||||||
|
uses: azure/setup-kubectl@v1
|
||||||
|
with:
|
||||||
|
version: 'v1.30.1'
|
||||||
|
|
||||||
|
- name: Authenticate with cluster
|
||||||
|
env:
|
||||||
|
CLUSTER_NAME: platform-${{ needs.extract_environment.outputs.environment }}
|
||||||
|
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
|
||||||
|
|
||||||
|
- name: Run Helmfile Apply
|
||||||
env:
|
env:
|
||||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
DEV_DOCS_PAGE_ID: ${{ secrets.DEV_DOCS_PAGE_ID }}
|
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||||
STG_DOCS_PAGE_ID: ${{ secrets.STG_DOCS_PAGE_ID }}
|
run: helmfile -f helmfiles/${ENV}.yaml sync --set image.tag=$IMAGE_TAG
|
||||||
PRD_DOCS_PAGE_ID: ${{ secrets.PRD_DOCS_PAGE_ID }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ $ENV == "dev" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $DEV_DOCS_PAGE_ID)"
|
|
||||||
elif [ $ENV == "stg" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $STG_DOCS_PAGE_ID)"
|
|
||||||
elif [ $ENV == "prd" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $PRD_DOCS_PAGE_ID)"
|
|
||||||
fi
|
|
||||||
id: extract_docs_page_id
|
|
||||||
|
|
||||||
- name: Extract Docs BlockId
|
|
||||||
env:
|
|
||||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
|
||||||
DEV_DOCS_BLOCK_ID: ${{ secrets.DEV_DOCS_BLOCK_ID }}
|
|
||||||
STG_DOCS_BLOCK_ID: ${{ secrets.STG_DOCS_BLOCK_ID }}
|
|
||||||
PRD_DOCS_BLOCK_ID: ${{ secrets.PRD_DOCS_BLOCK_ID }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ $ENV == "dev" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $DEV_DOCS_BLOCK_ID)"
|
|
||||||
elif [ $ENV == "stg" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $STG_DOCS_BLOCK_ID)"
|
|
||||||
elif [ $ENV == "prd" ]; then
|
|
||||||
echo "##[set-output name=result;]$(echo $PRD_DOCS_BLOCK_ID)"
|
|
||||||
fi
|
|
||||||
id: extract_docs_block_id
|
|
||||||
|
|
||||||
- name: Generate API docs
|
|
||||||
env:
|
|
||||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
|
||||||
DOCS_URL: ${{ secrets.DOCS_URL }}
|
|
||||||
DOCS_API_TOKEN: ${{ secrets.DOCS_API_TOKEN }}
|
|
||||||
DOCS_PAGE_ID: ${{ steps.extract_docs_page_id.outputs.result }}
|
|
||||||
DOCS_BLOCK_ID: ${{ steps.extract_docs_block_id.outputs.result }}
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
SWAGGER=`cat swagger.txt`
|
|
||||||
curl --request POST \
|
|
||||||
--url $DOCS_URL \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data @<(cat <<EOF
|
|
||||||
{
|
|
||||||
"service": "$DOCS_API_TOKEN",
|
|
||||||
"pageId": "$DOCS_PAGE_ID",
|
|
||||||
"blockId": "$DOCS_BLOCK_ID",
|
|
||||||
"schema": $SWAGGER
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
- name: Cleanup Docker's Leftovers
|
|
||||||
if: always()
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
docker system prune
|
|
||||||
docker rmi -f $(docker images -aq)
|
|
||||||
|
|||||||
@@ -6,23 +6,18 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: self-hosted
|
runs-on: [self-hosted, prd]
|
||||||
|
env:
|
||||||
|
APP_NAME: ${{ github.event.repository.name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
run: docker build -t ${APP_NAME}_teste --target test .
|
||||||
APP_NAME: ${{ github.event.repository.name }}
|
|
||||||
run: |
|
|
||||||
docker build -t $APP_NAME --target test .
|
|
||||||
|
|
||||||
- name: Run Test
|
- name: Run Test
|
||||||
env:
|
run: docker run ${APP_NAME}_teste
|
||||||
ENV: test
|
|
||||||
APP_NAME: ${{ github.event.repository.name }}
|
|
||||||
run: |
|
|
||||||
docker run --rm --entrypoint="npm" $APP_NAME run test
|
|
||||||
|
|
||||||
- name: Remove Docker's Trash
|
- name: Remove Docker's Trash
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Validate K8S Modifications
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- stg
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
extract_environment:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
environment: ${{ steps.extract_environment.outputs.environment }}
|
||||||
|
steps:
|
||||||
|
- name: Extract Environment
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.pull_request.base.ref }}" == "main" ]; then
|
||||||
|
echo "environment=prd" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "${{ github.event.pull_request.base.ref }}" == "beta" ]; then
|
||||||
|
echo "environment=stg" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
id: extract_environment
|
||||||
|
|
||||||
|
helmfile-deploy:
|
||||||
|
needs: [extract_environment]
|
||||||
|
runs-on: [self-hosted, "prd-azure"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary
|
||||||
|
env:
|
||||||
|
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
|
run: |
|
||||||
|
echo "### :rocket: Deploy da branch \`$GITHUB_REF_NAME\` para o environment ($ENV)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v1
|
||||||
|
with:
|
||||||
|
version: 'v3.9.0'
|
||||||
|
|
||||||
|
- name: Install Azure ClI
|
||||||
|
run: |
|
||||||
|
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
|
||||||
|
|
||||||
|
- uses: azure/login@v2
|
||||||
|
with:
|
||||||
|
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.8'
|
||||||
|
|
||||||
|
- name: Install Helmfile
|
||||||
|
run: |
|
||||||
|
wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz
|
||||||
|
tar -xzf helmfile_0.148.0_linux_amd64.tar.gz
|
||||||
|
mv helmfile /usr/local/bin/
|
||||||
|
helmfile --version
|
||||||
|
|
||||||
|
- name: Install Helm Diff Plugin
|
||||||
|
run: helm plugin install https://github.com/databus23/helm-diff || true
|
||||||
|
|
||||||
|
- name: Setup kubectl
|
||||||
|
uses: azure/setup-kubectl@v1
|
||||||
|
with:
|
||||||
|
version: 'v1.30.1'
|
||||||
|
|
||||||
|
- name: Authenticate with cluster
|
||||||
|
env:
|
||||||
|
CLUSTER_NAME: platform-${{ needs.extract_environment.outputs.environment }}
|
||||||
|
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
|
||||||
|
|
||||||
|
- name: Run Helmfile Diff
|
||||||
|
env:
|
||||||
|
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||||
|
run: helmfile -f helmfiles/${ENV}.yaml diff
|
||||||
Vendored
+1
@@ -12,6 +12,7 @@
|
|||||||
"start:debug"
|
"start:debug"
|
||||||
],
|
],
|
||||||
"runtimeExecutable": "npm",
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeVersion": "18.17",
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
],
|
],
|
||||||
|
|||||||
+38
-16
@@ -1,21 +1,43 @@
|
|||||||
# install all dependencies
|
FROM node:18.17-alpine AS base_image
|
||||||
FROM node:18.3.0-alpine3.15 as packages
|
|
||||||
|
FROM base_image AS build_base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
RUN apk update
|
||||||
RUN apk add --no-cache aws-cli \
|
# needed packages to build dependencies from source
|
||||||
&& aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \
|
RUN apk add --no-cache aws-cli
|
||||||
&& npm install
|
COPY package*.json ./
|
||||||
|
|
||||||
|
|
||||||
|
# run aws cli without mounting secret, because CI already has AWS credentials
|
||||||
|
FROM build_base AS ci_image
|
||||||
|
RUN aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
|
||||||
# unit test specific build
|
# unit test specific build
|
||||||
FROM node:18.3.0-alpine3.15 as test
|
FROM ci_image AS test
|
||||||
WORKDIR /app
|
ENV DUC_URL=0.0.0.0:50051
|
||||||
COPY --from=packages /app/node_modules ./node_modules
|
ENTRYPOINT ["npm", "run", "test"]
|
||||||
COPY . ./
|
|
||||||
|
|
||||||
FROM node:18.3.0-alpine3.15
|
|
||||||
WORKDIR /app
|
# dev build
|
||||||
COPY . /app/
|
FROM build_base AS dev
|
||||||
COPY --from=packages /app/node_modules ./node_modules
|
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||||
|
aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1
|
||||||
|
# flag --build-from-source is required to force-build sqlite3
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
ENTRYPOINT npm run start:dev
|
||||||
|
|
||||||
|
|
||||||
|
FROM ci_image AS prod_build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
EXPOSE 3333
|
|
||||||
ENTRYPOINT npm run start
|
|
||||||
|
FROM base_image
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=prod_build /app/dist ./dist
|
||||||
|
COPY --from=prod_build /app/node_modules ./node_modules
|
||||||
|
COPY --from=prod_build /app/package*.json ./
|
||||||
|
ENTRYPOINT npm run start:prod
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
LICENSE
|
||||||
|
Copyright (C) 2023 Dadosfera
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any means.
|
||||||
|
|
||||||
|
Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.
|
||||||
|
|
||||||
|
If you have any questions about this, please contact Dadosfera.ai at legal@dadosfera.ai
|
||||||
@@ -2,43 +2,58 @@
|
|||||||
<image src="./assets/maestro.svg" style="width:10rem">
|
<image src="./assets/maestro.svg" style="width:10rem">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
# Maestro
|
# Maestro
|
||||||
Maestro is the Dadosfera's gateway, it's responsible for the communication between the frontend application and Dadosfera's microservices.
|
|
||||||
|
|
||||||
## 🚀 Starting
|
Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços.
|
||||||
These instructions will allow you to get a working copy of the project on your local machine for development and testing purposes.
|
|
||||||
|
|
||||||
### 📋 Requirements
|
```mermaid
|
||||||
- [NodeJS v18.3.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
|
graph TD;
|
||||||
- Request access to AWS Console dev account for **all services** (avoid gradually asking for each needed service. it will slow down your development cycle)
|
Frontend<-->Maestro;
|
||||||
- Create your Access Key on the "Security credentials" menu
|
Maestro<-->duc;
|
||||||
- Set the Access Key on your local development machine
|
Maestro<-->pi-factory;
|
||||||
- Request access to the dev, stg and prd VPNs
|
Maestro<-->in-factory;
|
||||||
|
|
||||||
### 🔧 Installation<a id="installation"></a>
|
|
||||||
- Clone the repository
|
|
||||||
- SSH
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
É uma API REST, desenvolvida em NodeJs utilizando o Framework [NestJs](https://docs.nestjs.com/).
|
||||||
|
|
||||||
|
## 🚀 Iniciando
|
||||||
|
|
||||||
|
Estas instruções permitirão que você obtenha uma cópia funcional do projeto em sua máquina local para desenvolvimento e testes.
|
||||||
|
|
||||||
|
### 📋 Requisitos
|
||||||
|
|
||||||
|
- [NodeJS v18.17 / NPM v9.6.7](https://nodejs.org/pt-br/download/)
|
||||||
|
|
||||||
|
> Dica: Utilize [NVM](https://github.com/nvm-sh/nvm) para gerenciar facilmente as versões do node
|
||||||
|
|
||||||
|
- Solicite acesso à conta de desenvolvimento do AWS Console para **todos os serviços necessários**
|
||||||
|
- Crie sua Access Key no menu "Security credentials"
|
||||||
|
- Defina a Access Key em sua máquina de desenvolvimento local
|
||||||
|
- Solicite acesso às VPNs de stg e prd
|
||||||
|
|
||||||
|
### 🔧 Instalação<a id="installation"></a>
|
||||||
|
|
||||||
|
- Clone o repositório
|
||||||
|
|
||||||
|
```sh
|
||||||
git clone git@github.com:dadosfera/maestro.git
|
git clone git@github.com:dadosfera/maestro.git
|
||||||
```
|
```
|
||||||
or
|
|
||||||
|
|
||||||
- HTTPS
|
- Selecione a versão correta do node (opcional, apenas se estiver usando o [NVM](https://github.com/nvm-sh/nvm)):
|
||||||
```
|
|
||||||
git clone https://github.com/dadosfera/maestro.git
|
|
||||||
```
|
|
||||||
|
|
||||||
- Select the correct node version (optional, only if using [NVM](https://github.com/nvm-sh/nvm)):
|
|
||||||
```sh
|
```sh
|
||||||
nvm use
|
nvm use
|
||||||
```
|
```
|
||||||
|
|
||||||
- Install the project dependencies:
|
- Instale as dependências do projeto:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm i
|
npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
- Setup the following enviroment variables:
|
- Configure as seguintes variáveis de ambiente:
|
||||||
|
|
||||||
```
|
```
|
||||||
ENV=
|
ENV=
|
||||||
DUC_URL=
|
DUC_URL=
|
||||||
@@ -48,21 +63,41 @@ These instructions will allow you to get a working copy of the project on your l
|
|||||||
SM_OAUTH_PATH=
|
SM_OAUTH_PATH=
|
||||||
```
|
```
|
||||||
|
|
||||||
- Start the server:
|
- Inicie o servidor:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# dev mode
|
|
||||||
npm run start:dev
|
npm run start:dev
|
||||||
|
|
||||||
# or in debug mode
|
|
||||||
npm run start:debug
|
|
||||||
```
|
```
|
||||||
The service should start successfully.
|
|
||||||
|
|
||||||
## Authentication decorators
|
> Se preferir, utilize o debbuger do VSCode apertando F5
|
||||||
Maestro have utilities to ease the user authentication on every controller and route. The following decorators are available:
|
|
||||||
|
O serviço deve iniciar com sucesso.
|
||||||
|
|
||||||
|
## 📄 Documentação
|
||||||
|
|
||||||
|
O NestJs facilita a documentação de cada rota usando decoradores em todas as propriedades de solicitações e respostas. Ele gera automaticamente um swagger para as informações fornecidas e fornece uma rota para acessá-lo em http://localhost:3333/api. Para mais informações, consulte a [documentação oficial](https://docs.nestjs.com/openapi/introduction).
|
||||||
|
|
||||||
|
### - Documentações múltiplas
|
||||||
|
|
||||||
|
Atualmente, estamos gerando **2 documentações diferentes**: Interna e Externa.
|
||||||
|
|
||||||
|
Todas as rotas que têm o decorador `@ApiInternalOnly()` não serão visíveis no swagger da API **Externa**.
|
||||||
|
|
||||||
|
- Quando você inicia a aplicação com `npm run start:dev`, ela servirá e gerará o JSON do swagger da API **Interna**
|
||||||
|
|
||||||
|
- Quando você executa `npm run docs`, ele gerará o JSON do swagger de ambas as APIs **Interna** e **Externa** e os salvará em `docsfera.json` e `docsfera.external.json`, respectivamente;
|
||||||
|
|
||||||
|
> Link para documentação interna: https://dadosfera.github.io/docsfera
|
||||||
|
|
||||||
|
É importante executar `npm run docs` antes de cada implantação para que sempre tenhamos as documentações mais atualizadas publicadas.
|
||||||
|
|
||||||
|
## Decoradores de autenticação
|
||||||
|
|
||||||
|
O Maestro possui utilitários para facilitar a autenticação do usuário em todos os controladores e rotas. Os seguintes decoradores estão disponíveis:
|
||||||
|
|
||||||
### `@Authenticated`
|
### `@Authenticated`
|
||||||
If the user must be authenticated to make request, we can use the `@Authenticated` decorator in the controller or route, as needed.
|
|
||||||
|
Se o usuário precisar estar autenticado para fazer uma solicitação, podemos usar o decorador `@Authenticated` no controlador ou rota, conforme necessário.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Authenticated } from '../../authentication/authentication.decorator';
|
import { Authenticated } from '../../authentication/authentication.decorator';
|
||||||
@@ -74,7 +109,7 @@ class FooController {
|
|||||||
|
|
||||||
@Get('bar')
|
@Get('bar')
|
||||||
async getBar() {
|
async getBar() {
|
||||||
this.logger.info('user is authenticated!');
|
this.logger.info('usuário está autenticado!');
|
||||||
|
|
||||||
return { authenticated: true };
|
return { authenticated: true };
|
||||||
}
|
}
|
||||||
@@ -91,14 +126,14 @@ class FooController {
|
|||||||
@Get('bar')
|
@Get('bar')
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
async getBar() {
|
async getBar() {
|
||||||
this.logger.info('user is authenticated!');
|
this.logger.info('usuário está autenticado!');
|
||||||
|
|
||||||
return { authenticated: true };
|
return { authenticated: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('bar')
|
@Post('bar')
|
||||||
async postBar() {
|
async postBar() {
|
||||||
this.logger.info('user is NOT authenticated!');
|
this.logger.info('usuário NÃO está autenticado!');
|
||||||
|
|
||||||
return { authenticated: false };
|
return { authenticated: false };
|
||||||
}
|
}
|
||||||
@@ -106,7 +141,8 @@ class FooController {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `@RequireAllPermissions`
|
### `@RequireAllPermissions`
|
||||||
This decorator requires that **all permissions** listed are granted to the requesting user.
|
|
||||||
|
Este decorador exige que **todas as permissões** listadas sejam concedidas ao usuário solicitante.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { RequireAllPermissions } from '../../authentication/authentication.decorator';
|
import { RequireAllPermissions } from '../../authentication/authentication.decorator';
|
||||||
@@ -125,7 +161,8 @@ class FooController {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `@RequireSomePermission`
|
### `@RequireSomePermission`
|
||||||
In the following case, the user is required to have **at least one** listed permission.
|
|
||||||
|
No caso seguinte, é necessário que o usuário tenha **pelo menos uma** das permissões listadas.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { RequireSomePermission } from '../../authentication/authentication.decorator';
|
import { RequireSomePermission } from '../../authentication/authentication.decorator';
|
||||||
@@ -144,17 +181,19 @@ class FooController {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `@AuthenticateCondition`
|
### `@AuthenticateCondition`
|
||||||
If a more complicated authentication check needs to be done, we can use the `@AuthenticateCondition` decorator to define it. The custom function must return `true` to authenticate the request.
|
|
||||||
|
|
||||||
In the following example:
|
Se for necessária uma verificação de autenticação mais complicada, podemos usar o decorador `@AuthenticateCondition` para defini-la. A função personalizada deve retornar `true` para autenticar a solicitação.
|
||||||
- all routes on the `FooController` controller can only be requested from localhost
|
|
||||||
- `POST /foo/bar` can only be requested from localhost **and** by users from customer id `111...eef`
|
No exemplo a seguir:
|
||||||
|
|
||||||
|
- todas as rotas no controlador `FooController` só podem ser solicitadas a partir do localhost
|
||||||
|
- `POST /foo/bar` só pode ser solicitado a partir do localhost **e** por usuários do cliente com id `111...eef`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
|
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
|
||||||
|
|
||||||
@Controller('foo')
|
@Controller('foo')
|
||||||
// allow requests only from localhost
|
// permitir solicitações apenas do localhost
|
||||||
@AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1')
|
@AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1')
|
||||||
class FooController {
|
class FooController {
|
||||||
/* ... */
|
/* ... */
|
||||||
@@ -165,18 +204,22 @@ class FooController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('bar')
|
@Post('bar')
|
||||||
// allow requests only from a specific customer
|
// permitir solicitações apenas de um cliente específico
|
||||||
@AuthenticateCondition((request: Request, user: RequestUser) => user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef')
|
@AuthenticateCondition(
|
||||||
|
(request: Request, user: RequestUser) =>
|
||||||
|
user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef',
|
||||||
|
)
|
||||||
async postBar() {
|
async postBar() {
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Note on authentication decorators
|
### Nota sobre decoradores de autenticação
|
||||||
- The old authentication method placed the user data in the `request.body.info` field, this imposes certain issues regarding the request body because this data should be from the frontend without any modification by Maestro. Now this usage is ⚠️ **DEPRECATED** ⚠️. We are working to migrate to the `@User` parameter decorator. The old method is working while the migration is in progress.
|
|
||||||
|
|
||||||
- Authentication decorators can be used together and all of them **must** pass to the request be authenticated, but in the general case you don't need to (*and wouldn't like to...*) use all of them together, as you can code all of the authentication logic in the `@AuthenticateCondition` decorator.
|
- O método antigo de autenticação colocava os dados do usuário no campo `request.body.info`, o que impõe certos problemas em relação ao corpo da solicitação, pois esses dados devem vir do frontend sem qualquer modificação pelo Maestro. Agora, esse uso está ⚠️ **DESCONTINUADO** ⚠️. Estamos trabalhando para migrar para o decorador de parâmetro `@User`. O método antigo está funcionando enquanto a migração está em andamento.
|
||||||
|
|
||||||
|
- Os decoradores de autenticação podem ser usados juntos e todos eles **devem** passar para que a solicitação seja autenticada, mas, no caso geral, você não precisa (_e não gostaria de..._) usar todos eles juntos, pois você pode codificar toda a lógica de autenticação no decorador `@AuthenticateCondition`.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {
|
import {
|
||||||
@@ -185,7 +228,7 @@ class FooController {
|
|||||||
AuthenticateCondition,
|
AuthenticateCondition,
|
||||||
} from '../../authentication/authentication.decorator';
|
} from '../../authentication/authentication.decorator';
|
||||||
import { PERMISSIONS } from '../../authentication/permissions.enum';
|
import { PERMISSIONS } from '../../authentication/permissions.enum';
|
||||||
import { Waa, Baz } from './authenticationFunctions'
|
import { Waa, Baz } from './authenticationFunctions';
|
||||||
|
|
||||||
@Controller('foo')
|
@Controller('foo')
|
||||||
@RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST)
|
@RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST)
|
||||||
@@ -201,25 +244,28 @@ class FooController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('bar')
|
@Post('bar')
|
||||||
@RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.USE, Permissions.BAR.REQUEST)
|
@RequireAllPermissions(
|
||||||
|
Permissions.BAR.MANAGE,
|
||||||
|
Permissions.BAR.USE,
|
||||||
|
Permissions.BAR.REQUEST,
|
||||||
|
)
|
||||||
async postBar() {
|
async postBar() {
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- If `@RequireAllPermissions` and `@RequireSomePermission` are used with **only a single permission**, they present the **exactly same behavior**.
|
- Se `@RequireAllPermissions` e `@RequireSomePermission` forem usados com **apenas uma única permissão**, eles apresentam o **exatamente mesmo comportamento**.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// same behavior
|
// mesmo comportamento
|
||||||
@RequireAllPermissions(Permissions.BAR.MANAGE)
|
@RequireAllPermissions(Permissions.BAR.MANAGE)
|
||||||
@RequireSomePermission(Permissions.BAR.MANAGE)
|
@RequireSomePermission(Permissions.BAR.MANAGE)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Decorador de parâmetro `@User`
|
||||||
|
|
||||||
|
Os dados do usuário solicitante podem ser obtidos usando o decorador de parâmetro @User, como no exemplo a seguir:
|
||||||
## `@User` parameter decorator
|
|
||||||
The requesting user data can be obtained using the @User parameter decorator, like in the following snippet:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { User, RequestUser } from '../../authentication/user.decorator';
|
import { User, RequestUser } from '../../authentication/user.decorator';
|
||||||
@@ -237,7 +283,7 @@ class FooController {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If the user is required to be logged in, set `required` to `true`, as you would want in the `change-password` operation:
|
Se o usuário precisar estar logado, defina `required` como `true`, como por exemplo:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { User, RequestUser } from '../../authentication/user.decorator';
|
import { User, RequestUser } from '../../authentication/user.decorator';
|
||||||
@@ -263,39 +309,46 @@ class UserController {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📦 Development
|
## 📦 Desenvolvimento
|
||||||
### ⌨️ Coding Style
|
|
||||||
By default, we use [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) with default settings.
|
|
||||||
|
|
||||||
**We recommend using Visual Studio Code and installing the recommended extensions to ease the development process.**
|
### ⌨️ Estilo de Codificação
|
||||||
|
|
||||||
### Commits pattern
|
Por padrão, usamos [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) com configurações padrão.
|
||||||
Our workflow pipeline follows the conventional commits specs ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) to release versions accordingly.
|
|
||||||
|
|
||||||
Format: `<type>[optional scope]: <description>`
|
**Recomendamos usar o Visual Studio Code e instalar as extensões recomendadas para facilitar o processo de desenvolvimento.**
|
||||||
|
|
||||||
Example: `FIX: ensure Range headers adhere more closely to RFC 2616`
|
### Padrão de commits
|
||||||
|
|
||||||
### Branching naming convention
|
Nosso pipeline de fluxo de trabalho segue as especificações de commits convencionais ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) para liberar versões conforme necessário.
|
||||||
- **Feature**: Any code changes for a new module or use case should be done on a feature branch. This branch is created based on the `main` branch. When all changes are done, a Pull Request/Merge Request is needed to put all of these changes back to the `main` branch. Examples: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`.
|
|
||||||
|
|
||||||
**It is recommended to use all lower caps letters and hyphen (-) to separate words unless it is a specific item name or ID. Underscore (_) could be used to separate the ID and description.**
|
Formato: `<type>[optional scope]: <description>`
|
||||||
|
|
||||||
- **Bug Fix**: If the code changes made from the feature branch were rejected after a release, sprint or demo, any necessary fixes after that should be done on the bugfix branch. Examples: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`.
|
Exemplo: `FIX: ensure Range headers adhere more closely to RFC 2616`
|
||||||
|
|
||||||
- **Hot Fix**: If there is a need to fix a blocker, do a temporary patch, apply a critical framework or configuration change that should be handled immediately, it should be created as a Hotfix. Examples: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`.
|
### Convenção de nomenclatura de branchs
|
||||||
|
|
||||||
- **Experimental**: A branch for playing around. Any new feature or idea that is not part of a release or a sprint. Example: `experimental/dark-theme-support`.
|
- **Feature**: Quaisquer alterações de código para um novo módulo ou caso de uso devem ser feitas em uma branch de feature. Esta branch é criada com base na branch `main`. Quando todas as alterações estiverem concluídas, será necessário um Pull Request/Merge Request para colocar todas essas alterações de volta na branch `main`. Exemplos: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`.
|
||||||
|
|
||||||
### Making a Pull Request
|
**Recomenda-se usar todas as letras em minúsculas e hífen (-) para separar palavras, a menos que seja um nome ou ID de item específico. O sublinhado (\_) pode ser usado para separar o ID e a descrição.**
|
||||||
1. Commit your changes
|
|
||||||
2. Open the Pull Request on GitHub
|
|
||||||
3. Send Pull Request link in microsfera Google Chat Group for review and possible approval
|
|
||||||
|
|
||||||
## 🛠️ Built with
|
- **Bug Fix**: Se as alterações de código feitas na branch de feature foram rejeitadas após um lançamento, sprint ou demo, quaisquer correções necessárias após isso devem ser feitas na branch de correção de bug. Exemplos: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`.
|
||||||
Some technologies used in this project:
|
|
||||||
|
|
||||||
- [NestJS](https://docs.nestjs.com) - Framework for building efficient and scalable NodeJS server-side applications
|
- **Hot Fix**: Se houver necessidade de corrigir um bloqueador, fazer um patch temporário, aplicar uma mudança crítica de framework ou configuração que deva ser tratada imediatamente, ela deve ser criada como um Hotfix. Exemplos: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`.
|
||||||
|
|
||||||
## ⚙️ Back-end Architecture
|
- **Experimental**: Uma branch para experimentar. Qualquer nova feature ou ideia que não faça parte de um lançamento ou sprint. Exemplo: `experimental/dark-theme-support`.
|
||||||
The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).
|
|
||||||
|
### Fazendo um Pull Request
|
||||||
|
|
||||||
|
1. Comite suas alterações
|
||||||
|
2. Abra o Pull Request no GitHub
|
||||||
|
3. Envie o link do Pull Request no grupo de chat do Google da Microsfera para revisão e possível aprovação
|
||||||
|
|
||||||
|
## 🛠️ Construído com
|
||||||
|
|
||||||
|
Algumas tecnologias usadas neste projeto:
|
||||||
|
|
||||||
|
- [NestJS](https://docs.nestjs.com) - Framework para construir aplicações NodeJS eficientes e escaláveis no lado do servidor
|
||||||
|
|
||||||
|
## ⚙️ Arquitetura de Back-end
|
||||||
|
|
||||||
|
A arquitetura pode ser encontrada neste [link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
services:
|
||||||
|
maestro:
|
||||||
|
build: .
|
||||||
|
image: dadosfera/maestro_${ENV}:${IMAGE_TAG}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
maestro:
|
maestro:
|
||||||
build: .
|
build: .
|
||||||
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
|
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_prd:${IMAGE_TAG}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
services:
|
||||||
|
maestro:
|
||||||
|
image: dadosfera/maestro_${ENV}:${IMAGE_TAG}
|
||||||
|
container_name: maestro
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 3333:3333
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
node_exporter:
|
||||||
|
image: ${NODE_EXPORTER_URL}
|
||||||
|
container_name: node_exporter
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /proc:/host/proc:ro
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /:/rootfs:ro
|
||||||
|
- /run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket:ro
|
||||||
|
command:
|
||||||
|
- '--path.procfs=/host/proc'
|
||||||
|
- '--path.rootfs=/rootfs'
|
||||||
|
- '--path.sysfs=/host/sys'
|
||||||
|
- '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
|
||||||
|
- '--collector.systemd'
|
||||||
|
- '--collector.processes'
|
||||||
|
network_mode: host
|
||||||
|
pid: host
|
||||||
+1
-2
@@ -1,4 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
maestro:
|
maestro:
|
||||||
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
|
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
|
||||||
@@ -10,7 +9,7 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
|
|
||||||
node_exporter:
|
node_exporter:
|
||||||
image: prom/node-exporter:v1.3.1
|
image: ${NODE_EXPORTER_URL}
|
||||||
container_name: node_exporter
|
container_name: node_exporter
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+8405
File diff suppressed because it is too large
Load Diff
Vendored
+20
@@ -0,0 +1,20 @@
|
|||||||
|
declare global {
|
||||||
|
namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
NODE_ENV: 'test';
|
||||||
|
ENV: 'local' | 'stg' | 'prd' | 'test';
|
||||||
|
LOCAL_ENV: 'stg' | 'prd';
|
||||||
|
|
||||||
|
DUC_URL: string;
|
||||||
|
INFACTORY_URL: string;
|
||||||
|
PIFACTORY_URL: string;
|
||||||
|
|
||||||
|
INTERNAL_SWAGGER: 'true' | 'false';
|
||||||
|
|
||||||
|
AWS_REGION: string;
|
||||||
|
OPEN_GROUP_ID: string;
|
||||||
|
OPEN_CUSTOMER_ID: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
charts:
|
||||||
|
- name: maestro
|
||||||
|
chart: ../maestro
|
||||||
|
values:
|
||||||
|
- ../maestro/values.yaml
|
||||||
|
set:
|
||||||
|
- name: maestro.duc_url
|
||||||
|
value: duc.dadosfera.ai
|
||||||
|
- name: hostname
|
||||||
|
value: maestro.dadosfera.ai
|
||||||
|
- name: maestro.pi_factory_url
|
||||||
|
value: pi-factory.dadosfera.ai
|
||||||
|
- name: maestro.in_factory_url
|
||||||
|
value: in-factory.dadosfera.ai
|
||||||
|
- name: maestro.tr_factory_url
|
||||||
|
value: in-factory.dadosfera.ai
|
||||||
|
- name: maestro.open_customer_id
|
||||||
|
value: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e
|
||||||
|
- name: maestro.open_group_id
|
||||||
|
value: 401573bb-334f-44b2-b30e-88d4cea31ae9
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
charts:
|
||||||
|
- name: maestro
|
||||||
|
chart: ../maestro
|
||||||
|
values:
|
||||||
|
- ../maestro/values.yaml
|
||||||
|
set:
|
||||||
|
- name: maestro.duc_url
|
||||||
|
value: duc.stg.dadosfera.ai
|
||||||
|
- name: hostname
|
||||||
|
value: maestro.stg.dadosfera.ai
|
||||||
|
- name: maestro.pi_factory_url
|
||||||
|
value: pi-factory.dadosfera.ai
|
||||||
|
- name: maestro.in_factory_url
|
||||||
|
value: in-factory.dadosfera.ai
|
||||||
|
- name: maestro.tr_factory_url
|
||||||
|
value: in-factory.dadosfera.ai
|
||||||
|
- name: maestro.open_customer_id
|
||||||
|
value: b3e3dfe5-b992-4586-a73c-c0b0c00f615d
|
||||||
|
- name: maestro.open_group_id
|
||||||
|
value: e3f98a2f-7748-4981-8505-7695c8ca8218
|
||||||
|
- name: replicaCount
|
||||||
|
value: 1
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: maestro
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "1.16.0"
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: maestro
|
||||||
|
namespace: applications
|
||||||
|
labels:
|
||||||
|
app: maestro
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: maestro
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: maestro
|
||||||
|
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: {{ .Values.imagePullSecrets }}
|
||||||
|
nodeSelector:
|
||||||
|
"beta.kubernetes.io/os": linux
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: application
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- backend
|
||||||
|
tolerations:
|
||||||
|
- key: "kubernetes.azure.com/scalesetpriority"
|
||||||
|
operator: "Equal"
|
||||||
|
value: "spot"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: maestro
|
||||||
|
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.containerPort }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: {{ .Values.resources.requests.cpu }}
|
||||||
|
memory: {{ .Values.resources.requests.memory }}
|
||||||
|
limits:
|
||||||
|
cpu: {{ .Values.resources.limits.cpu }}
|
||||||
|
memory: {{ .Values.resources.limits.memory }}
|
||||||
|
env:
|
||||||
|
- name: AWS_IDENTITY_POOL_ID
|
||||||
|
value: {{ .Values.maestro.aws_identity_pool_id }}
|
||||||
|
- name: AWS_REGION
|
||||||
|
value: "us-east-1"
|
||||||
|
- name: BASE_HOST
|
||||||
|
value: "maestro_prd"
|
||||||
|
- name: BUCKET_CUSTOMER_CSV_ASSETS
|
||||||
|
value: {{ .Values.maestro.bucket_customer_csv_assets }}
|
||||||
|
- name: CONNECTORS_INDEX
|
||||||
|
value: "connectors"
|
||||||
|
- name: DUC_URL
|
||||||
|
value: {{ .Values.maestro.duc_url }}
|
||||||
|
- name: ELASTIC_APM_ENVIRONMENT
|
||||||
|
value: {{ .Values.maestro.env }}
|
||||||
|
- name: ELASTIC_APM_SERVER_URL
|
||||||
|
value: "https://apm-server.dadosfera.ai"
|
||||||
|
- name: ELASTIC_APM_SERVICE_NAME
|
||||||
|
value: {{ .Values.maestro.apm_service }}
|
||||||
|
- name: ENV
|
||||||
|
value: {{ .Values.maestro.env }}
|
||||||
|
- name: INFACTORY_URL
|
||||||
|
value: {{ .Values.maestro.in_factory_url }}
|
||||||
|
- name: LOGGER_GELF_HOST
|
||||||
|
value: "logstash-pipelines.dadosfera.ai"
|
||||||
|
- name: LOGGER_GELF_PORT
|
||||||
|
value: "{{ .Values.maestro.logger_gelf_port }}"
|
||||||
|
- name: NIMBUS_BASE_URL
|
||||||
|
value: "http://nimbus-api"
|
||||||
|
- name: NPM_TOKEN
|
||||||
|
value: {{ .Values.maestro.npm_token }}
|
||||||
|
- name: PB_TOKEN_PATH
|
||||||
|
value: {{ .Values.maestro.pb_token_path }}
|
||||||
|
- name: PIFACTORY_URL
|
||||||
|
value: {{ .Values.maestro.pi_factory_url }}
|
||||||
|
- name: SM_OAUTH_PATH
|
||||||
|
value: {{ .Values.maestro.sm_oauth_path }}
|
||||||
|
- name: TRFACTORY_URL
|
||||||
|
value: {{ .Values.maestro.tr_factory_url }}
|
||||||
|
- name: UPLOAD_FILE_AGENT_CONNECTION
|
||||||
|
value: {{ .Values.maestro.upload_file_agent_connection }}
|
||||||
|
- name: JWT_PRIVATE_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: prd-duc
|
||||||
|
key: jwt_token
|
||||||
|
- name: AWS_ACCESS_KEY_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: prd-maestro
|
||||||
|
key: AWS_ACCESS_KEY_ID
|
||||||
|
- name: AWS_SECRET_ACCESS_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: prd-maestro
|
||||||
|
key: AWS_SECRET_ACCESS_KEY
|
||||||
|
- name: AWS_DEFAULT_REGION
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: prd-maestro
|
||||||
|
key: AWS_DEFAULT_REGION
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||||
|
nginx.ingress.kubernetes.io/server-snippet: |
|
||||||
|
underscores_in_headers on;
|
||||||
|
ignore_invalid_headers on;
|
||||||
|
|
||||||
|
generation: 1
|
||||||
|
labels:
|
||||||
|
app: maestro
|
||||||
|
name: maestro
|
||||||
|
namespace: applications
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: {{ .Values.hostname }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: maestro
|
||||||
|
port:
|
||||||
|
number: {{ .Values.ingress.port }}
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: prd-maestro
|
||||||
|
namespace: applications
|
||||||
|
labels:
|
||||||
|
app: maestro
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: secretsmanager-prd
|
||||||
|
kind: SecretStore
|
||||||
|
target:
|
||||||
|
name: prd-maestro
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: AWS_ACCESS_KEY_ID
|
||||||
|
remoteRef:
|
||||||
|
key: prd/microservices/aws_credentials/maestro
|
||||||
|
version: "AWSCURRENT"
|
||||||
|
property: AWS_ACCESS_KEY_ID
|
||||||
|
|
||||||
|
- secretKey: AWS_SECRET_ACCESS_KEY
|
||||||
|
remoteRef:
|
||||||
|
key: prd/microservices/aws_credentials/maestro
|
||||||
|
version: "AWSCURRENT"
|
||||||
|
property: AWS_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
|
- secretKey: AWS_DEFAULT_REGION
|
||||||
|
remoteRef:
|
||||||
|
key: prd/microservices/aws_credentials/maestro
|
||||||
|
version: "AWSCURRENT"
|
||||||
|
property: AWS_DEFAULT_REGION
|
||||||
|
|
||||||
|
- secretKey: jwt_token
|
||||||
|
remoteRef:
|
||||||
|
key: {{ .Values.maestro.jwt_token_secret_id }}
|
||||||
|
version: "AWSCURRENT"
|
||||||
|
property: token
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: maestro
|
||||||
|
namespace: applications
|
||||||
|
labels:
|
||||||
|
app: maestro
|
||||||
|
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: maestro
|
||||||
|
protocol: TCP
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: {{ .Values.service.targetPort }}
|
||||||
|
selector:
|
||||||
|
app: maestro
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Default values for metabase.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 3
|
||||||
|
hostname: maestro-temp.dadosfera.ai
|
||||||
|
image:
|
||||||
|
repository: 611330257153.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_prd
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: 1.56.0
|
||||||
|
containerPort: 3333
|
||||||
|
imagePullSecrets: "applications-secrets-ecr-auth-token-external-secret"
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3333
|
||||||
|
targetPort: 3333
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
port: 3333
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 1500Mi
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 2Gi
|
||||||
|
maestro:
|
||||||
|
aws_identity_pool_id: "us-east-1_Mrezsw9Sn"
|
||||||
|
duc_url: duc.dadosfera.ai
|
||||||
|
in_factory_url: in-factory.dadosfera.ai
|
||||||
|
bucket_customer_csv_assets: "customers-csv-assets-prd-611330257153"
|
||||||
|
env: prd
|
||||||
|
apm_service: maestro
|
||||||
|
jwt_token_secret_id: prd/root/jwt_token
|
||||||
|
logger_gelf_port: "1026"
|
||||||
|
npm_token: npm_Xp17h3daMkORcZ55NY95Ez4gRfdzsQ3UvINP
|
||||||
|
pb_token_path: prd/root/productboard_token
|
||||||
|
pi_factory_url: pi-factory.dadosfera.ai
|
||||||
|
sm_oauth_path: prd/root/oauth_applications
|
||||||
|
tr_factory_url: in-factory.dadosfera.ai
|
||||||
|
upload_file_agent_connection: cbc2f881-58c4-4d60-8003-0979b0b5b911
|
||||||
|
open_customer_id: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e
|
||||||
|
open_group_id: 401573bb-334f-44b2-b30e-88d4cea31ae9
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 100
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
targetMemoryUtilizationPercentage: 80
|
||||||
Generated
+4329
-11510
File diff suppressed because it is too large
Load Diff
+50
-47
@@ -6,91 +6,94 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.3.0"
|
"node": "18.17"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1",
|
"co:login": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1",
|
||||||
|
"proto-update": "npm i @dadosfera/protospack-v2@latest --save-exact",
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "export INTERNAL_SWAGGER=true && nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "npm run start:dev",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
|
"docs": "export KILL_AFTER_START=1 && nest start && export INTERNAL_SWAGGER=true && nest start",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest --detectOpenHandles --forceExit",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.112.0",
|
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
||||||
"@dadosfera/dadosfera-logs": "^1.0.0-beta.3",
|
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
||||||
"@dadosfera/protospack-v2": "3.17.5",
|
"@dadosfera/protospack": "2.5.3",
|
||||||
"@grpc/grpc-js": "^1.6.7",
|
"@dadosfera/protospack-v2": "3.37.0-beta.3",
|
||||||
"@grpc/proto-loader": "^0.6.13",
|
"@grpc/grpc-js": "^1.9.3",
|
||||||
"@nestjs/common": "^8.4.7",
|
"@grpc/proto-loader": "^0.7.9",
|
||||||
"@nestjs/config": "^1.2.1",
|
"@nestjs/cli": "^9.5.0",
|
||||||
"@nestjs/core": "^8.4.7",
|
"@nestjs/common": "^9.4.3",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/config": "^2.3.4",
|
||||||
"@nestjs/microservices": "^8.4.7",
|
"@nestjs/core": "^9.4.3",
|
||||||
"@nestjs/passport": "^8.2.2",
|
"@nestjs/mapped-types": "^1.2.2",
|
||||||
"@nestjs/platform-express": "^8.4.7",
|
"@nestjs/microservices": "^9.4.3",
|
||||||
"@nestjs/schedule": "^1.1.0",
|
"@nestjs/passport": "^9.0.3",
|
||||||
"@nestjs/swagger": "^5.2.1",
|
"@nestjs/platform-express": "^9.4.3",
|
||||||
|
"@nestjs/schematics": "^9.2.0",
|
||||||
|
"@nestjs/swagger": "^6.3.0",
|
||||||
|
"@nestjs/testing": "^9.4.3",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.14.0",
|
||||||
"cron-parser": "^4.4.0",
|
"cron-parser": "^4.9.0",
|
||||||
"dotenv": "^14.3.2",
|
"dotenv": "^14.3.2",
|
||||||
"elastic-apm-node": "^3.36.0",
|
"elastic-apm-node": "^3.50.0",
|
||||||
"helmet": "^5.1.0",
|
"helmet": "^5.1.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jwk-to-pem": "^2.0.5",
|
"jwk-to-pem": "^2.0.5",
|
||||||
|
"mixpanel": "^0.17.0",
|
||||||
|
"ms": "^3.0.0-canary.1",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-facebook": "^3.0.0",
|
"passport-facebook": "^3.0.0",
|
||||||
"passport-forcedotcom": "^0.1.5",
|
"passport-forcedotcom": "^0.2.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-hubspot-oauth2": "^1.0.3",
|
"passport-hubspot-oauth2": "^1.0.3",
|
||||||
"passport-mailchimp": "^1.1.0",
|
"passport-mailchimp": "^1.1.0",
|
||||||
"protospack": "2.5.2",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.5.5",
|
||||||
"swagger-ui-express": "^4.4.0"
|
"swagger-ui-express": "^4.6.3"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"multer": "1.4.5-lts.1"
|
"multer": "1.4.5-lts.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^8.2.8",
|
"@types/express": "^4.17.17",
|
||||||
"@nestjs/schematics": "^8.0.11",
|
|
||||||
"@nestjs/testing": "^8.4.7",
|
|
||||||
"@types/express": "^4.17.13",
|
|
||||||
"@types/jest": "27.0.2",
|
"@types/jest": "27.0.2",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/jwk-to-pem": "^2.0.1",
|
"@types/jwk-to-pem": "^2.0.1",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^16.11.41",
|
"@types/node": "^16.18.52",
|
||||||
"@types/passport-facebook": "^2.1.11",
|
"@types/passport-facebook": "^2.1.11",
|
||||||
"@types/passport-google-oauth20": "^2.0.11",
|
"@types/passport-google-oauth20": "^2.0.11",
|
||||||
"@types/passport-oauth2": "^1.4.11",
|
"@types/passport-oauth2": "^1.4.12",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.29.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"eslint": "^8.18.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"nock": "^13.2.7",
|
"nock": "^13.3.3",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.8",
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.2.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^27.1.5",
|
"ts-jest": "^27.1.5",
|
||||||
"ts-loader": "^9.3.1",
|
"ts-loader": "^9.4.4",
|
||||||
"ts-node": "^10.8.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^3.14.1",
|
"tsconfig-paths": "^3.14.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-8
@@ -23,9 +23,13 @@ import { HealthModule } from './modules/health/health.module';
|
|||||||
import { CatalogModule } from './modules/catalog/catalog.module';
|
import { CatalogModule } from './modules/catalog/catalog.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
|
import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
|
||||||
|
import { ProductboardModule } from './modules/productboard/productboard.module';
|
||||||
|
import { MixpanelModule } from './modules/mixpanel/mixpanel.module';
|
||||||
|
import { CustomersModule } from './modules/customers/customers.module';
|
||||||
|
import { OpenDataModule } from './modules/open-data/open-data.module';
|
||||||
|
import { ThemeModule } from './modules/theme/theme.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [],
|
|
||||||
providers: [
|
providers: [
|
||||||
DadosferaLogger,
|
DadosferaLogger,
|
||||||
{
|
{
|
||||||
@@ -37,22 +41,28 @@ import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
|
|||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
ConnectorModule,
|
|
||||||
AuthModule,
|
AuthModule,
|
||||||
PermissionsModule,
|
|
||||||
TermsOfUseModule,
|
|
||||||
ConnectionModule,
|
ConnectionModule,
|
||||||
NetworkConfigModule,
|
NetworkConfigModule,
|
||||||
|
InputsModule,
|
||||||
|
PipelinesV2Module,
|
||||||
|
CatalogModule,
|
||||||
|
ConnectorModule,
|
||||||
|
PermissionsModule,
|
||||||
|
TermsOfUseModule,
|
||||||
ConnectionTestModule,
|
ConnectionTestModule,
|
||||||
PipelinesModule,
|
PipelinesModule,
|
||||||
TransformationsModule,
|
TransformationsModule,
|
||||||
HealthModule,
|
|
||||||
CatalogModule,
|
|
||||||
UsersModule,
|
UsersModule,
|
||||||
RolesModule,
|
RolesModule,
|
||||||
InputsModule,
|
|
||||||
OauthModule,
|
OauthModule,
|
||||||
PipelinesV2Module,
|
ProductboardModule,
|
||||||
|
MixpanelModule,
|
||||||
|
CustomersModule,
|
||||||
|
OpenDataModule,
|
||||||
|
ThemeModule,
|
||||||
|
//Always leave HealthModule last, so it is on the bottom of swagger
|
||||||
|
HealthModule
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import { Request } from 'express';
|
|
||||||
import { CustomDecorator } from '@nestjs/common';
|
|
||||||
import { PermissionsObjectPermission } from './permissions.enum';
|
|
||||||
import { RequestUser } from './user.decorator';
|
|
||||||
|
|
||||||
export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__';
|
|
||||||
|
|
||||||
export type AuthenticationFunction = (req: Request, user: any) => boolean;
|
|
||||||
|
|
||||||
// implementation copied from SetMetadata, but tweaked to get existing values
|
|
||||||
// of the metadataKey and accumulate it with the new metadataValue
|
|
||||||
function SetMultipleMetadata(metadataKey, metadataValue): CustomDecorator {
|
|
||||||
const decoratorFactory = (target, key, descriptor) => {
|
|
||||||
// .start: tweak
|
|
||||||
// descriptor?.value = function decorator; target = class decorator
|
|
||||||
const accumulatedVal =
|
|
||||||
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
|
|
||||||
accumulatedVal.push(metadataValue);
|
|
||||||
// .end: tweak
|
|
||||||
|
|
||||||
if (descriptor) {
|
|
||||||
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
decoratorFactory.KEY = metadataKey;
|
|
||||||
return decoratorFactory as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RequireAllPermissions(
|
|
||||||
...permissions: PermissionsObjectPermission[]
|
|
||||||
) {
|
|
||||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
|
|
||||||
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RequireSomePermission(
|
|
||||||
...permissions: PermissionsObjectPermission[]
|
|
||||||
) {
|
|
||||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
|
|
||||||
permissions.some(({ seqid }) => user.permissions.includes(seqid)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AuthenticateCondition(func: AuthenticationFunction) {
|
|
||||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Authenticated() {
|
|
||||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, () => true);
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Authenticated,
|
Authenticated,
|
||||||
RequireAllPermissions,
|
RequireAllPermissions,
|
||||||
RequireSomePermission,
|
RequireSomePermission,
|
||||||
} from './authentication.decorator';
|
} from '../decorators/authentication.decorator';
|
||||||
import { AuthenticationGuard } from './authentication.guard';
|
import { AuthenticationGuard } from './authentication.guard';
|
||||||
import { PERMISSIONS_GROUPS } from './permissions.enum';
|
import { PERMISSIONS_GROUPS } from './permissions.enum';
|
||||||
import { AuthClientService } from '../modules/auth/auth.service';
|
import { AuthClientService } from '../modules/auth/auth.service';
|
||||||
@@ -44,7 +44,7 @@ class NoClassAuthController {
|
|||||||
@Get('has-all-permissions')
|
@Get('has-all-permissions')
|
||||||
@RequireAllPermissions(
|
@RequireAllPermissions(
|
||||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||||
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
|
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||||
)
|
)
|
||||||
async hasAllPermissions(@Body() body) {
|
async hasAllPermissions(@Body() body) {
|
||||||
return { body };
|
return { body };
|
||||||
@@ -53,7 +53,7 @@ class NoClassAuthController {
|
|||||||
@Get('has-some-permission')
|
@Get('has-some-permission')
|
||||||
@RequireSomePermission(
|
@RequireSomePermission(
|
||||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||||
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
|
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||||
)
|
)
|
||||||
async hasSomePermission(@Body() body) {
|
async hasSomePermission(@Body() body) {
|
||||||
return { body };
|
return { body };
|
||||||
@@ -62,7 +62,7 @@ class NoClassAuthController {
|
|||||||
|
|
||||||
@Controller('class-auth-condition')
|
@Controller('class-auth-condition')
|
||||||
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
|
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
|
||||||
@RequireSomePermission(PERMISSIONS_GROUPS.METABASE.permissions.OPEN)
|
@RequireSomePermission(PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE)
|
||||||
class ClassAuthConditionController {
|
class ClassAuthConditionController {
|
||||||
@Get('body')
|
@Get('body')
|
||||||
async getBody(@Body() body) {
|
async getBody(@Body() body) {
|
||||||
@@ -444,13 +444,13 @@ describe('authentication.guard', () => {
|
|||||||
NoClassAuthTest(tokenZ, ['zendesk']);
|
NoClassAuthTest(tokenZ, ['zendesk']);
|
||||||
ClassAuthConditionTest(tokenZ, ['zendesk']);
|
ClassAuthConditionTest(tokenZ, ['zendesk']);
|
||||||
|
|
||||||
const tokenM = CreateToken([PERMISSIONS_GROUPS.METABASE.permissions.OPEN]);
|
const tokenM = CreateToken([PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE]);
|
||||||
NoClassAuthTest(tokenM, ['metabase']);
|
NoClassAuthTest(tokenM, ['metabase']);
|
||||||
ClassAuthConditionTest(tokenM, ['metabase']);
|
ClassAuthConditionTest(tokenM, ['metabase']);
|
||||||
|
|
||||||
const tokenZM = CreateToken([
|
const tokenZM = CreateToken([
|
||||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||||
PERMISSIONS_GROUPS.METABASE.permissions.OPEN,
|
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||||
]);
|
]);
|
||||||
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
|
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
|
||||||
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
|
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { AuthClientService } from '../modules/auth/auth.service';
|
|||||||
import {
|
import {
|
||||||
AuthenticationFunction,
|
AuthenticationFunction,
|
||||||
AUTH_FUNCTION_KEY,
|
AUTH_FUNCTION_KEY,
|
||||||
} from './authentication.decorator';
|
} from '../decorators/authentication.decorator';
|
||||||
import { RequestUser } from './user.decorator';
|
import { RequestUser } from '../decorators/user.decorator';
|
||||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||||
import ErrorCodes from '../utils/errorCodes';
|
import ErrorCodes from '../utils/errorCodes';
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,18 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
'es-es': '',
|
'es-es': '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
GENERATE_TOKEN: {
|
||||||
|
seqid: 46,
|
||||||
|
claim: 'customer:generate-token',
|
||||||
|
usage: PermissionUsages.PUBLIC,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Gerar Token de Acesso',
|
||||||
|
'en-us': 'Generate Acess Token',
|
||||||
|
'es-es': 'Gerar Token de Acceso',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
PIPELINE: {
|
PIPELINE: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Coletar | Pipelines',
|
'pt-br': 'Coletar | Pipelines',
|
||||||
@@ -107,7 +116,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
CONNECTION: {
|
CONNECTION: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Coletar | Fontes de dados',
|
'pt-br': 'Coletar | Fontes de dados',
|
||||||
@@ -125,17 +133,16 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
'es-es': 'Crear Source',
|
'es-es': 'Crear Source',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//TODO descomentar quando tiver ok o DELETE de conexões
|
DELETE: {
|
||||||
// DELETE: {
|
seqid: 38,
|
||||||
// seqid: 38,
|
claim: 'connection:delete',
|
||||||
// claim: 'connection:delete',
|
usage: PermissionUsages.PUBLIC,
|
||||||
// usage: PermissionUsages.PUBLIC,
|
name: {
|
||||||
// name: {
|
'pt-br': 'Excluir Fonte',
|
||||||
// 'pt-br': 'Excluir Fonte',
|
'en-us': 'Remove Source',
|
||||||
// 'en-us': 'Remove Source',
|
'es-es': 'Eliminar Source',
|
||||||
// 'es-s': 'Eliminar Source',
|
},
|
||||||
// },
|
},
|
||||||
// },
|
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
seqid: 39,
|
seqid: 39,
|
||||||
claim: 'connection:update',
|
claim: 'connection:update',
|
||||||
@@ -148,7 +155,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
NETWORK_CONFIG: {
|
NETWORK_CONFIG: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Coletar | Redes',
|
'pt-br': 'Coletar | Redes',
|
||||||
@@ -178,7 +184,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
// },
|
// },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
OUTPUT: {
|
OUTPUT: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Output',
|
'pt-br': 'Output',
|
||||||
@@ -228,7 +233,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
TRANSFORMATIONS: {
|
TRANSFORMATIONS: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Micro-transformações',
|
'pt-br': 'Micro-transformações',
|
||||||
@@ -278,7 +282,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
CATALOG: {
|
CATALOG: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Explorar | Catálogo',
|
'pt-br': 'Explorar | Catálogo',
|
||||||
@@ -291,44 +294,74 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
claim: 'GET /catalog',
|
claim: 'GET /catalog',
|
||||||
usage: PermissionUsages.PUBLIC,
|
usage: PermissionUsages.PUBLIC,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Buscar data assets',
|
'pt-br': 'Acessar lista de Ativos e visualizar atributos',
|
||||||
'en-us': 'Search for data assets',
|
'en-us': 'Access Assets list and its attributes',
|
||||||
'es-es': 'Buscar data assets',
|
'es-es': 'Acceda a la lista de Activos y vea los atributos',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
seqid: 19,
|
seqid: 19,
|
||||||
claim: 'POST /catalog',
|
claim: 'catalog:create',
|
||||||
usage: PermissionUsages.PUBLIC,
|
usage: PermissionUsages.PUBLIC,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Criar atributos no catálogo',
|
'pt-br': 'Criar Ativos',
|
||||||
'en-us': 'Create attributes',
|
'en-us': 'Create Assets',
|
||||||
'es-es': 'Crear atributos',
|
'es-es': 'Crear Activos',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
seqid: 25,
|
seqid: 25,
|
||||||
claim: 'PUT /catalog',
|
claim: 'catalog:edit',
|
||||||
usage: PermissionUsages.INTERNAL,
|
usage: PermissionUsages.PUBLIC,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Editar atributos no catálogo',
|
'pt-br': 'Criar e editar atributos no catálogo',
|
||||||
'en-us': 'Edit attributes',
|
'en-us': 'Create and edit attributes on the catalog',
|
||||||
'es-es': 'Editar atributos',
|
'es-es': 'Crear y editar atributos en el catálogo',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
seqid: 1,
|
seqid: 1,
|
||||||
claim: 'DELETE /catalog',
|
claim: 'catalog:delete',
|
||||||
|
usage: PermissionUsages.PUBLIC,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Excluir Ativos do catálogo',
|
||||||
|
'en-us': 'Remove Assets from the catalog',
|
||||||
|
'es-es': 'Eliminar Activos del catálogo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DATA_MANAGER: {
|
||||||
|
seqid: 42,
|
||||||
|
claim: 'catalog:data_manager',
|
||||||
usage: PermissionUsages.INTERNAL,
|
usage: PermissionUsages.INTERNAL,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Excluir atributos no catálogo',
|
'pt-br':
|
||||||
'en-us': 'Remove attributes',
|
'Gerente do catálogo. Consegue vizualizar e editar todos os ativos.',
|
||||||
'es-es': 'Eliminar atributos',
|
'en-us': 'Catalog manager. Able to view and edit all assets.',
|
||||||
|
'es-es': 'Gestor de catálogos. Puede ver y editar todos los activos.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EMBED_ANALYTICS: {
|
||||||
|
seqid: 44,
|
||||||
|
claim: 'catalog:embed',
|
||||||
|
usage: PermissionUsages.INTERNAL,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Acessar Módulo de Incorporação de Ativos',
|
||||||
|
'en-us': 'Access Embedding analytics Module',
|
||||||
|
'es-es': 'Acceder al Módulo de Incorporación de Activos',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TRIGGER_CATALOG_TASK: {
|
||||||
|
seqid: 45,
|
||||||
|
claim: 'catalog:trigger-task',
|
||||||
|
usage: PermissionUsages.INTERNAL,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Executar a catalogação de um ativo',
|
||||||
|
'en-us': 'Trigger an asset cataloging',
|
||||||
|
'es-es': 'Realizar el listado de un activo',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
CONNECTORS: {
|
CONNECTORS: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Conectores',
|
'pt-br': 'Conectores',
|
||||||
@@ -378,7 +411,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
SNOWFLAKE: {
|
SNOWFLAKE: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Explorar | Consolidar',
|
'pt-br': 'Explorar | Consolidar',
|
||||||
@@ -398,7 +430,6 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ZENDESK: {
|
ZENDESK: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Zendesk',
|
'pt-br': 'Zendesk',
|
||||||
@@ -408,25 +439,24 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
permissions: {
|
permissions: {
|
||||||
OPEN: {
|
OPEN: {
|
||||||
seqid: 16,
|
seqid: 16,
|
||||||
claim: 'GET /zendesk',
|
claim: 'support-chat:access',
|
||||||
usage: PermissionUsages.INTERNAL,
|
usage: PermissionUsages.INTERNAL,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Acessar Zendesk',
|
'pt-br': 'Acessar chat de suporte',
|
||||||
'en-us': 'Access Zendesk',
|
'en-us': 'Access support chat',
|
||||||
'es-es': 'Acceso Zendesk',
|
'es-es': 'Acceder al chat de soporte',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DATAVIZ: {
|
||||||
METABASE: {
|
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Analisar | Visualização',
|
'pt-br': 'Analisar | Visualização',
|
||||||
'en-us': 'Analyze | Visualization',
|
'en-us': 'Analyze | Visualization',
|
||||||
'es-es': 'Analizar | Visualización',
|
'es-es': 'Analizar | Visualización',
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
OPEN: {
|
METABASE: {
|
||||||
seqid: 11,
|
seqid: 11,
|
||||||
claim: 'GET /metabase',
|
claim: 'GET /metabase',
|
||||||
usage: PermissionUsages.PUBLIC,
|
usage: PermissionUsages.PUBLIC,
|
||||||
@@ -438,8 +468,7 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
INTELLIGENCE: {
|
||||||
ORCHEST: {
|
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Analisar | Inteligência',
|
'pt-br': 'Analisar | Inteligência',
|
||||||
'en-us': 'Analyze | Intelligence',
|
'en-us': 'Analyze | Intelligence',
|
||||||
@@ -449,13 +478,22 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
INTELLIGENCE: {
|
INTELLIGENCE: {
|
||||||
seqid: 31,
|
seqid: 31,
|
||||||
claim: 'intelligence:open',
|
claim: 'intelligence:open',
|
||||||
usage: PermissionUsages.INTERNAL,
|
usage: PermissionUsages.PUBLIC,
|
||||||
name: {
|
name: {
|
||||||
'pt-br': 'Acessar Orchest',
|
'pt-br': 'Acessar Módulo de Inteligência',
|
||||||
'en-us': 'Access Orchest',
|
'en-us': 'Access Intelligence Module',
|
||||||
'es-s': 'Acceso Orchest',
|
'es-s': 'Acceder Módulo de Inteligencia',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MODULES: {
|
||||||
|
title: {
|
||||||
|
'pt-br': 'Módulos da Dadosfera',
|
||||||
|
'en-us': 'Dadosfera Modules',
|
||||||
|
'es-es': 'Módulos de la Dadosfera',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
APP_BUILDER: {
|
APP_BUILDER: {
|
||||||
seqid: 32,
|
seqid: 32,
|
||||||
claim: 'app-builder:open',
|
claim: 'app-builder:open',
|
||||||
@@ -463,7 +501,7 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
name: {
|
name: {
|
||||||
'pt-br': 'Acessar App Builder',
|
'pt-br': 'Acessar App Builder',
|
||||||
'en-us': 'Access App Builder',
|
'en-us': 'Access App Builder',
|
||||||
'es-es': 'Acceso App Builder',
|
'es-es': 'Acceder App Builder',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MACHINE_LEARNING: {
|
MACHINE_LEARNING: {
|
||||||
@@ -473,7 +511,7 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
name: {
|
name: {
|
||||||
'pt-br': 'Acessar Machine Learning',
|
'pt-br': 'Acessar Machine Learning',
|
||||||
'en-us': 'Access Machine Learning',
|
'en-us': 'Access Machine Learning',
|
||||||
'es-es': 'Acceso Machine Learning',
|
'es-es': 'Acceder Machine Learning',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -497,6 +535,25 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PROCESS: {
|
||||||
|
title: {
|
||||||
|
'pt-br': 'Processar',
|
||||||
|
'en-us': 'Process',
|
||||||
|
'es-es': 'Procesar',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
TRANSFORMATION: {
|
||||||
|
seqid: 43,
|
||||||
|
claim: 'process:open',
|
||||||
|
usage: PermissionUsages.PUBLIC,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Acessar Módulo de Transformação',
|
||||||
|
'en-us': 'Access Transformation Module',
|
||||||
|
'es-es': 'Acceder Módulo de transformación',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
DADOSFERA: {
|
DADOSFERA: {
|
||||||
title: {
|
title: {
|
||||||
'pt-br': 'Usuários',
|
'pt-br': 'Usuários',
|
||||||
@@ -504,7 +561,7 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
'es-es': 'Usuarios',
|
'es-es': 'Usuarios',
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
ADMIN: {
|
CONNECTIONS_MIGRATION: {
|
||||||
seqid: 40,
|
seqid: 40,
|
||||||
claim: 'dadosfera:connections-migration',
|
claim: 'dadosfera:connections-migration',
|
||||||
usage: PermissionUsages.INTERNAL,
|
usage: PermissionUsages.INTERNAL,
|
||||||
@@ -514,10 +571,67 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
'es-es': 'connections-migration',
|
'es-es': 'connections-migration',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
SYNC_USER: {
|
||||||
|
seqid: 41,
|
||||||
|
claim: 'dadosfera:sync-user',
|
||||||
|
usage: PermissionUsages.INTERNAL,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'sync-user',
|
||||||
|
'en-us': 'sync-user',
|
||||||
|
'es-es': 'sync-user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CUSTOMER: {
|
||||||
|
title: {
|
||||||
|
'pt-br': 'Organização',
|
||||||
|
'en-us': 'Organization',
|
||||||
|
'es-es': 'Organización',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
MONITORING_DASHBOARD: {
|
||||||
|
seqid: 47,
|
||||||
|
claim: 'customer:monitoring-dashboard',
|
||||||
|
usage: PermissionUsages.PUBLIC,
|
||||||
|
name: {
|
||||||
|
'pt-br': 'Ver o dashboard de monitoramento',
|
||||||
|
'en-us': 'View the monitoring dashboard',
|
||||||
|
'es-es': 'Ver el dashboard de monitoreo',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export interface DadosferaModule {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
key: string;
|
||||||
|
permissionSeqId: number;
|
||||||
|
}
|
||||||
|
export const DADOSFERA_MODULES: Array<DadosferaModule> = [
|
||||||
|
{
|
||||||
|
name: 'Intelligence Module',
|
||||||
|
description: 'Orchest Module',
|
||||||
|
key: 'intelligence',
|
||||||
|
permissionSeqId:
|
||||||
|
PERMISSIONS_GROUPS.INTELLIGENCE.permissions.INTELLIGENCE.seqid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Proccessing Module',
|
||||||
|
description: 'Proccessing Module',
|
||||||
|
key: 'process',
|
||||||
|
permissionSeqId:
|
||||||
|
PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Embedded Analytics',
|
||||||
|
description: 'Embedded Analytics Module',
|
||||||
|
key: 'embedded-analytics',
|
||||||
|
permissionSeqId:
|
||||||
|
PERMISSIONS_GROUPS.PROCESS.permissions.TRANSFORMATION.seqid,
|
||||||
|
},
|
||||||
|
];
|
||||||
// traverses the object searching for duplicate seqids or claims (executes at runtime)
|
// traverses the object searching for duplicate seqids or claims (executes at runtime)
|
||||||
let nextAvailableSeqid = 0;
|
let nextAvailableSeqid = 0;
|
||||||
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||||
@@ -537,4 +651,9 @@ Object.values(PERMISSIONS_GROUPS).map((namespace) =>
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DADOSFERA_MODULES.map((m) => m.key).forEach((m, i, arr) => {
|
||||||
|
if (arr.indexOf(m) !== i)
|
||||||
|
throw new Error(`DADOSFERA_MODULES[${i}] does not have a unique key`);
|
||||||
|
});
|
||||||
|
|
||||||
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
|
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { applyDecorators } from '@nestjs/common';
|
||||||
|
import { ApiSecurity } from '@nestjs/swagger';
|
||||||
|
import { type Request } from 'express';
|
||||||
|
import { type PermissionsObjectPermission } from '../authentication/permissions.enum';
|
||||||
|
import { type RequestUser } from './user.decorator';
|
||||||
|
import { SetMultipleMetadata } from './shared';
|
||||||
|
|
||||||
|
export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__';
|
||||||
|
|
||||||
|
export type AuthenticationFunction = (req: Request, user: any) => boolean;
|
||||||
|
|
||||||
|
export function RequireAllPermissions(
|
||||||
|
...permissions: PermissionsObjectPermission[]
|
||||||
|
) {
|
||||||
|
return createAuthenticatedDecorator((req, user: RequestUser) =>
|
||||||
|
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RequireSomePermission(
|
||||||
|
...permissions: PermissionsObjectPermission[]
|
||||||
|
) {
|
||||||
|
return createAuthenticatedDecorator((req, user: RequestUser) =>
|
||||||
|
permissions.some(({ seqid }) => user.permissions.includes(seqid)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AuthenticateCondition(func: AuthenticationFunction) {
|
||||||
|
return createAuthenticatedDecorator(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Authenticated() {
|
||||||
|
return createAuthenticatedDecorator(() => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAuthenticatedDecorator(metadataValue: AuthenticationFunction) {
|
||||||
|
return applyDecorators(
|
||||||
|
ApiSecurity('access-token'),
|
||||||
|
SetMultipleMetadata(AUTH_FUNCTION_KEY, metadataValue),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
|
||||||
|
export const Language: () => ParameterDecorator = createParamDecorator(
|
||||||
|
(options: any, ctx: ExecutionContext): LanguageEnum => {
|
||||||
|
const headers = ctx.switchToHttp()?.getRequest()?.headers;
|
||||||
|
|
||||||
|
if (!headers) return LanguageEnum.ptbr;
|
||||||
|
|
||||||
|
let language: LanguageEnum =
|
||||||
|
headers['dadosfera-lang'] ?? headers['accept-language'];
|
||||||
|
language = language?.toLowerCase().trim() as LanguageEnum;
|
||||||
|
|
||||||
|
if (!language) return LanguageEnum.ptbr;
|
||||||
|
|
||||||
|
Object.values(LanguageEnum).some((l) => {
|
||||||
|
if (language === l || l.includes(language)) {
|
||||||
|
language = l;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
language = LanguageEnum.enus;
|
||||||
|
});
|
||||||
|
return language;
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const SetOrigin = (origin: string) => SetMetadata('allowedOrigin', origin);
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CORSGuard implements CanActivate {
|
||||||
|
constructor(private reflector: Reflector) {}
|
||||||
|
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const response = context.switchToHttp().getResponse();
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const origin = request.headers.origin;
|
||||||
|
|
||||||
|
// Obter a origem permitida através do decorador
|
||||||
|
const allowedOrigin = this.reflector.get<string>('allowedOrigin', context.getHandler());
|
||||||
|
if (process.env.ENV === "local") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica se a origem da requisição é permitida
|
||||||
|
if (allowedOrigin && origin !== allowedOrigin) {
|
||||||
|
throw new ForbiddenException('Acesso não permitido pela política CORS');
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { type CustomDecorator } from '@nestjs/common';
|
||||||
|
|
||||||
|
// implementation copied from SetMetadata, but tweaked to get existing values
|
||||||
|
// of the metadataKey and accumulate it with the new metadataValue
|
||||||
|
export function SetMultipleMetadata(
|
||||||
|
metadataKey,
|
||||||
|
metadataValue,
|
||||||
|
): CustomDecorator {
|
||||||
|
const decoratorFactory: CustomDecorator = (target, key?, descriptor?) => {
|
||||||
|
// .start: tweak
|
||||||
|
// descriptor?.value = function decorator; target = class decorator
|
||||||
|
const accumulatedVal =
|
||||||
|
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
|
||||||
|
accumulatedVal.push(metadataValue);
|
||||||
|
// .end: tweak
|
||||||
|
|
||||||
|
if (descriptor) {
|
||||||
|
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
decoratorFactory.KEY = metadataKey;
|
||||||
|
return decoratorFactory;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { applyDecorators } from '@nestjs/common';
|
||||||
|
import { ApiExcludeController, ApiExcludeEndpoint } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export function ApiInternalOnlyEndpoint() {
|
||||||
|
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||||
|
return applyDecorators(ApiExcludeEndpoint());
|
||||||
|
return () => null;
|
||||||
|
}
|
||||||
|
export function ApiInternalOnlyController() {
|
||||||
|
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||||
|
return applyDecorators(ApiExcludeController());
|
||||||
|
return () => null;
|
||||||
|
}
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
import request from 'supertest';
|
|
||||||
import { Test } from '@nestjs/testing';
|
|
||||||
import { HttpStatus, INestApplication } from '@nestjs/common';
|
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
|
|
||||||
import { Controller, Get } from '@nestjs/common';
|
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { AuthenticationGuard } from './authentication.guard';
|
import { Controller, Get, HttpStatus, INestApplication } from '@nestjs/common';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import request from 'supertest';
|
||||||
|
import { AuthenticationGuard } from '../authentication/authentication.guard';
|
||||||
|
import { PERMISSIONS_GROUPS } from '../authentication/permissions.enum';
|
||||||
import { AuthClientService } from '../modules/auth/auth.service';
|
import { AuthClientService } from '../modules/auth/auth.service';
|
||||||
|
|
||||||
import ErrorCodes from '../utils/errorCodes';
|
import ErrorCodes from '../utils/errorCodes';
|
||||||
import { User } from './user.decorator';
|
import { User } from './user.decorator';
|
||||||
import { PERMISSIONS_GROUPS } from './permissions.enum';
|
|
||||||
|
|
||||||
const logger = {
|
const logger = {
|
||||||
info: (...args) => args,
|
info: (...args) => args,
|
||||||
@@ -49,7 +46,7 @@ describe('user.decorator', () => {
|
|||||||
const fakeUserPayload = {
|
const fakeUserPayload = {
|
||||||
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
|
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
|
||||||
username: 'super.admin',
|
username: 'super.admin',
|
||||||
permissions: [PERMISSIONS_GROUPS.METABASE.permissions.OPEN].map(
|
permissions: [PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE].map(
|
||||||
({ seqid }) => seqid,
|
({ seqid }) => seqid,
|
||||||
),
|
),
|
||||||
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
|
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
|
||||||
|
|
||||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||||
import ErrorCodes from '../utils/errorCodes';
|
import ErrorCodes from '../utils/errorCodes';
|
||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Catch,
|
Catch,
|
||||||
ArgumentsHost,
|
ArgumentsHost,
|
||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
|||||||
+48
-14
@@ -6,6 +6,8 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
|||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
DadosferaLogger.setupLogger({
|
DadosferaLogger.setupLogger({
|
||||||
serviceName: 'maestro',
|
serviceName: 'maestro',
|
||||||
@@ -22,28 +24,60 @@ async function bootstrap() {
|
|||||||
optionsSuccessStatus: 204,
|
optionsSuccessStatus: 204,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
configureSwagger(app);
|
||||||
|
await app.listen(3333);
|
||||||
|
if (process.env.KILL_AFTER_START) await app.close();
|
||||||
|
}
|
||||||
|
|
||||||
const date = new Date();
|
function configureSwagger(app: INestApplication) {
|
||||||
|
let branchName = '';
|
||||||
|
try {
|
||||||
|
branchName = execSync('git rev-parse --abbrev-ref HEAD')
|
||||||
|
.toString()
|
||||||
|
.replace('\n', '')
|
||||||
|
.trim();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Can't get branch name", error);
|
||||||
|
}
|
||||||
|
const swaggerTitle = branchName ? `Maestro - ${branchName}` : 'Maestro';
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const swaggerConfig = new DocumentBuilder()
|
||||||
.setTitle('Maestro')
|
.setTitle(swaggerTitle)
|
||||||
.setDescription('Documentation for Maestro gateway')
|
.setDescription('Documentation for Maestro gateway')
|
||||||
.setVersion(
|
.addSecurity('access-token', {
|
||||||
`${date.getDate()}.${
|
type: 'apiKey',
|
||||||
date.getMonth() + 1
|
name: 'Authorization',
|
||||||
}.${date.getFullYear()} - ${date.getHours()}:${date.getMinutes()}`,
|
in: 'header',
|
||||||
)
|
})
|
||||||
.addBearerAuth()
|
.setDescription('This is the Maestro API');
|
||||||
.build();
|
|
||||||
|
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||||
|
swaggerConfig.addServer('https://maestro.dadosfera.ai', 'Dadosfera API');
|
||||||
|
|
||||||
|
const config = swaggerConfig.build();
|
||||||
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
|
||||||
|
const swaggerJsonFile =
|
||||||
|
process.env.INTERNAL_SWAGGER === 'true'
|
||||||
|
? 'docsfera.json'
|
||||||
|
: 'docsfera.external.json';
|
||||||
|
|
||||||
if (process.env.ENV === 'local') SwaggerModule.setup('api', app, document);
|
if (process.env.ENV === 'local') SwaggerModule.setup('api', app, document);
|
||||||
|
|
||||||
await writeFileSync('swagger.txt', JSON.stringify(JSON.stringify(document)));
|
writeFileSync(
|
||||||
|
swaggerJsonFile,
|
||||||
await app.listen(3333);
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
service: '${DOCS_API_TOKEN}',
|
||||||
|
pageId: '${DOCS_PAGE_ID}',
|
||||||
|
blockId: '${DOCS_BLOCK_ID}',
|
||||||
|
schema: document,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Auth
|
||||||
|
|
||||||
|
## SSO/oAuth
|
||||||
|
|
||||||
|
### Strategy
|
||||||
|
We are using [PassportJs](https://www.passportjs.org/) to handle oAuth authentications.
|
||||||
|
|
||||||
|
When the client (front end) makes a `GET /auth/oauth/{strategy}` Passport automatically redirects the user to the `strategy` login page. To do that we must configure and use a **Passport Strategy**. We must also have a callback route, conventionally `GET /auth/oauth/{strategy}/callback`, so the oAuth app can report the status of the user's login.
|
||||||
|
|
||||||
|
- If the oAuth is successfull we call DUC's `AuthOauthSignIn` request that gets the tokens from Cognito and saves them on cache temporarily under a key we call `session`. Duc returns that `session` to maestro which then redirects the user to our app login page with that `session` as a query param.
|
||||||
|
|
||||||
|
- If the oAuth login is not successfull for some reason or the user **does not** exist on DUC's database we redirect the user to our login page with an `error` and `error_description` as query params.
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
So in order to have an SSO login, besides configuring the Strategy, we must have two routes for each Strategy, like in the example below:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
@Get('oauth/google')
|
||||||
|
@UseGuards(AuthGuard('google-login'))
|
||||||
|
googleOauth() {
|
||||||
|
this.logger.info('/oauth/google');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('oauth/google/callback')
|
||||||
|
@UseGuards(AuthGuard('google-login'))
|
||||||
|
@Redirect()
|
||||||
|
async googleOauthCallback(@Req() req) {
|
||||||
|
const { url, email, token, language = 'pt-br' } = await this.callback(req);
|
||||||
|
if (url.searchParams.get('error')) {
|
||||||
|
this.logger.error('/oauth/google - ERROR');
|
||||||
|
return { url: url.href };
|
||||||
|
}
|
||||||
|
// ... Rest of the logic
|
||||||
|
return { url: url.href };
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -7,11 +7,19 @@ import {
|
|||||||
HttpStatus,
|
HttpStatus,
|
||||||
Inject,
|
Inject,
|
||||||
UseFilters,
|
UseFilters,
|
||||||
|
Get,
|
||||||
|
UseGuards,
|
||||||
|
Redirect,
|
||||||
|
Req,
|
||||||
|
Param,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
import {
|
import {
|
||||||
AuthSignInRequest,
|
ApiHeaders,
|
||||||
AuthRefreshAccessTokenRequest,
|
ApiOkResponse,
|
||||||
|
ApiSecurity,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
AuthChangePasswordRequest,
|
AuthChangePasswordRequest,
|
||||||
AuthResetPasswordRequest,
|
AuthResetPasswordRequest,
|
||||||
AuthVerifyResetPasswordCodeRequest,
|
AuthVerifyResetPasswordCodeRequest,
|
||||||
@@ -22,17 +30,36 @@ import {
|
|||||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||||
|
|
||||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
import { RequireAllPermissions } from 'src/authentication/authentication.decorator';
|
import {
|
||||||
|
Authenticated,
|
||||||
|
RequireAllPermissions,
|
||||||
|
} from 'src/decorators/authentication.decorator';
|
||||||
import { AuthClientService } from './auth.service';
|
import { AuthClientService } from './auth.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter';
|
import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
|
import {
|
||||||
|
AuthRefreshAccessTokenReq,
|
||||||
|
AuthRefreshAccessTokenRes,
|
||||||
|
AuthSignInReq,
|
||||||
|
AuthSignInRes,
|
||||||
|
} from './dtos/login';
|
||||||
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import ErrorCodes, { OauthErrors } from 'src/utils/errorCodes';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
@ApiTags('Auth')
|
@ApiTags('Auth')
|
||||||
|
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
logger: DadosferaLogger;
|
logger: DadosferaLogger;
|
||||||
|
redirectUrl: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DadosferaLogger)
|
@Inject(DadosferaLogger)
|
||||||
@@ -40,8 +67,49 @@ export class AuthController {
|
|||||||
private authClient: AuthClientService,
|
private authClient: AuthClientService,
|
||||||
) {
|
) {
|
||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
|
|
||||||
|
switch (process.env.ENV) {
|
||||||
|
case 'stg':
|
||||||
|
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/auth/login`;
|
||||||
|
break;
|
||||||
|
case 'prd':
|
||||||
|
this.redirectUrl = `https://app.dadosfera.ai/auth/login`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.redirectUrl = `http://localhost:4200/auth/login`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('sign-in')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
async signIn(
|
||||||
|
@Body() { username, password, totp }: AuthSignInReq,
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
): Promise<AuthSignInRes> {
|
||||||
|
this.logger.info('/auth - SignIn');
|
||||||
|
const metadata = PackTheMetadata({ language });
|
||||||
|
return this.authClient.signIn({ username, password, totp }, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('refresh-access-token')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiOkResponse({ type: AuthRefreshAccessTokenRes })
|
||||||
|
async refreshAccessToken(
|
||||||
|
@Body() body: AuthRefreshAccessTokenReq,
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
) {
|
||||||
|
this.logger.info('/auth - RefreshAccessToken');
|
||||||
|
const { refreshToken, customerName: customer_name } = body;
|
||||||
|
|
||||||
|
const metadata = PackTheMetadata({
|
||||||
|
customer_name,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.authClient.refreshAccessToken({ refreshToken }, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('/sso/snowflake')
|
@Post('/sso/snowflake')
|
||||||
@RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN)
|
@RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN)
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@@ -57,24 +125,7 @@ export class AuthController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('sign-in')
|
@ApiInternalOnlyEndpoint()
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
async signIn(@Body() { username, password, totp }: AuthSignInRequest) {
|
|
||||||
this.logger.info('/auth - SignIn');
|
|
||||||
|
|
||||||
return this.authClient.signIn({ username, password, totp });
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('refresh-access-token')
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
async refreshAccessToken(
|
|
||||||
@Body() { refreshToken }: AuthRefreshAccessTokenRequest,
|
|
||||||
) {
|
|
||||||
this.logger.info('/auth - RefreshAccessToken');
|
|
||||||
|
|
||||||
return this.authClient.refreshAccessToken({ refreshToken });
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('change-password')
|
@Post('change-password')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async changePassword(
|
async changePassword(
|
||||||
@@ -93,16 +144,24 @@ export class AuthController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('reset-password')
|
@Post('reset-password')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async resetPassword(@Body() body: AuthResetPasswordRequest) {
|
async resetPassword(
|
||||||
|
@Body() body: AuthResetPasswordRequest,
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
) {
|
||||||
this.logger.info('/auth - reset-password');
|
this.logger.info('/auth - reset-password');
|
||||||
|
const metadata = PackTheMetadata({
|
||||||
|
language: language,
|
||||||
|
});
|
||||||
|
|
||||||
const { username } = body;
|
const { username } = body;
|
||||||
|
|
||||||
return this.authClient.resetPassword({ username });
|
return this.authClient.resetPassword({ username }, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('verify-reset-password-code')
|
@Post('verify-reset-password-code')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async verifyResetPasswordCode(
|
async verifyResetPasswordCode(
|
||||||
@@ -115,6 +174,7 @@ export class AuthController {
|
|||||||
return this.authClient.verifyResetPasswordCode({ username, code });
|
return this.authClient.verifyResetPasswordCode({ username, code });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('confirm-reset-password')
|
@Post('confirm-reset-password')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
|
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
|
||||||
@@ -129,6 +189,7 @@ export class AuthController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('enable-totp')
|
@Post('enable-totp')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async enableTotpMFA(
|
async enableTotpMFA(
|
||||||
@@ -143,6 +204,7 @@ export class AuthController {
|
|||||||
return this.authClient.enableTotpMFA({ accessToken, password });
|
return this.authClient.enableTotpMFA({ accessToken, password });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('disable-totp')
|
@Post('disable-totp')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async disableTotpMFA(
|
async disableTotpMFA(
|
||||||
@@ -157,6 +219,7 @@ export class AuthController {
|
|||||||
return this.authClient.disableTotpMFA({ accessToken, password });
|
return this.authClient.disableTotpMFA({ accessToken, password });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('dismiss-totp')
|
@Post('dismiss-totp')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async dismissTotpMFA(@Headers() headers) {
|
async dismissTotpMFA(@Headers() headers) {
|
||||||
@@ -167,6 +230,7 @@ export class AuthController {
|
|||||||
return this.authClient.dismissTotpMFA({ accessToken });
|
return this.authClient.dismissTotpMFA({ accessToken });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Post('verify-totp')
|
@Post('verify-totp')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
|
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
|
||||||
@@ -177,4 +241,107 @@ export class AuthController {
|
|||||||
|
|
||||||
return this.authClient.verifyTotp({ accessToken, totp });
|
return this.authClient.verifyTotp({ accessToken, totp });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Authenticated()
|
||||||
|
@ApiSecurity('access-token')
|
||||||
|
@Get('verify-access-token')
|
||||||
|
verifyAccessToken() {
|
||||||
|
return { access_token_status: 'valid' };
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Get('session/:session')
|
||||||
|
getSession(@Param('session') session: string) {
|
||||||
|
return this.authClient.getSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Get('oauth/google')
|
||||||
|
@UseGuards(AuthGuard('google-login'))
|
||||||
|
googleOauth() {
|
||||||
|
this.logger.info('/oauth/google');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Get('oauth/google/callback')
|
||||||
|
@UseGuards(AuthGuard('google-login'))
|
||||||
|
@Redirect()
|
||||||
|
async googleOauthCallback(@Req() req) {
|
||||||
|
const { url, email, token, language = 'pt-br' } = await this.callback(req);
|
||||||
|
if (url.searchParams.get('error')) {
|
||||||
|
this.logger.error('/oauth/google - ERROR');
|
||||||
|
return { url: url.href };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.authClient
|
||||||
|
.oauthSignIn({
|
||||||
|
username: email,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
.then(({ session }) => {
|
||||||
|
this.logger.info('/oauth/google - SUCESS');
|
||||||
|
url.searchParams.set('session', session);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.logger.error('/oauth/google - ERROR');
|
||||||
|
let error = OauthErrors.INVALID_CREDENTIALS[language].error;
|
||||||
|
let error_description =
|
||||||
|
OauthErrors.INVALID_CREDENTIALS[language].error_description;
|
||||||
|
switch (err.details) {
|
||||||
|
case ErrorCodes.USER.NOT_FOUND:
|
||||||
|
error = OauthErrors.USER_NOT_FOUND[language].error;
|
||||||
|
error_description =
|
||||||
|
OauthErrors.USER_NOT_FOUND[language].error_description(email);
|
||||||
|
break;
|
||||||
|
case ErrorCodes.AUTH.UNAUTHORIZED:
|
||||||
|
error = OauthErrors.INVALID_SESSION[language].error;
|
||||||
|
error_description =
|
||||||
|
OauthErrors.INVALID_SESSION[language].error_description;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
url.searchParams.set('error', error);
|
||||||
|
url.searchParams.set('error_description', error_description);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return { url: url.href };
|
||||||
|
}
|
||||||
|
|
||||||
|
async callback(req: Request) {
|
||||||
|
const { error, state } = req.query;
|
||||||
|
const { authInfo } = req;
|
||||||
|
const url = new URL(this.redirectUrl);
|
||||||
|
let email, token, error_title, error_description;
|
||||||
|
let language: 'pt-br' | 'en-us' = 'pt-br';
|
||||||
|
|
||||||
|
const stateObject = jwt.verify(
|
||||||
|
state as string,
|
||||||
|
process.env.JWT_PRIVATE_KEY,
|
||||||
|
);
|
||||||
|
if (typeof stateObject != 'string') language = stateObject.language;
|
||||||
|
|
||||||
|
if (error || !authInfo) {
|
||||||
|
this.logger.error(error);
|
||||||
|
if (!authInfo) this.logger.error('No authInfo', { request: req });
|
||||||
|
|
||||||
|
error_title = OauthErrors.INVALID_CREDENTIALS[language].error;
|
||||||
|
error_description =
|
||||||
|
OauthErrors.INVALID_CREDENTIALS[language].error_description;
|
||||||
|
if (error) error_description += ` - [${error}]`;
|
||||||
|
} else {
|
||||||
|
const { accessToken } = authInfo as any;
|
||||||
|
const { _json: userInfo } = req.user as any;
|
||||||
|
|
||||||
|
email = userInfo.email;
|
||||||
|
token = accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_title) {
|
||||||
|
url.searchParams.set('error', error_title);
|
||||||
|
url.searchParams.set('error_description', error_description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token, email, url, language };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,19 @@ import { AuthController } from './auth.controller';
|
|||||||
import { AuthClientService } from './auth.service';
|
import { AuthClientService } from './auth.service';
|
||||||
|
|
||||||
import { DucClient } from '../duc/client.config';
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
|
||||||
|
import { getOauthSecrets } from 'src/utils/OauthSecrets';
|
||||||
const client = new DucClient();
|
const client = new DucClient();
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ClientsModule.register([client.providerOptions])],
|
imports: [ClientsModule.register([client.providerOptions])],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthClientService, DadosferaLogger],
|
providers: [
|
||||||
|
AuthClientService,
|
||||||
|
DadosferaLogger,
|
||||||
|
GoogleLoginStrategy,
|
||||||
|
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
|
||||||
|
],
|
||||||
exports: [AuthClientService],
|
exports: [AuthClientService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
AuthConfirmResetPasswordRequest,
|
AuthConfirmResetPasswordRequest,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||||
import { DucClient } from '../duc/client.config';
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthClientService implements OnModuleInit {
|
export class AuthClientService implements OnModuleInit {
|
||||||
@@ -51,19 +52,25 @@ export class AuthClientService implements OnModuleInit {
|
|||||||
return lastValueFrom(this.authService.AuthSnowflakeSignIn(input));
|
return lastValueFrom(this.authService.AuthSnowflakeSignIn(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
async signIn({ username, password, totp }: AuthSignInRequest) {
|
async signIn(
|
||||||
|
{ username, password, totp }: AuthSignInRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
) {
|
||||||
this.logger.info('SignIn');
|
this.logger.info('SignIn');
|
||||||
|
|
||||||
return lastValueFrom(
|
return lastValueFrom(
|
||||||
this.authService.AuthSignIn({ username, password, totp }),
|
this.authService.AuthSignIn({ username, password, totp }, metadata),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshAccessToken({ refreshToken }: AuthRefreshAccessTokenRequest) {
|
async refreshAccessToken(
|
||||||
|
{ refreshToken }: AuthRefreshAccessTokenRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
) {
|
||||||
this.logger.info('RefreshAccessToken');
|
this.logger.info('RefreshAccessToken');
|
||||||
|
|
||||||
return lastValueFrom(
|
return lastValueFrom(
|
||||||
this.authService.AuthRefreshAccessToken({ refreshToken }),
|
this.authService.AuthRefreshAccessToken({ refreshToken }, metadata),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +90,15 @@ export class AuthClientService implements OnModuleInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword({ username }: AuthResetPasswordRequest) {
|
async resetPassword(
|
||||||
|
{ username }: AuthResetPasswordRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
) {
|
||||||
this.logger.info('resetPassword');
|
this.logger.info('resetPassword');
|
||||||
|
|
||||||
return lastValueFrom(this.authService.AuthResetPassword({ username }));
|
return lastValueFrom(
|
||||||
|
this.authService.AuthResetPassword({ username }, metadata),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyResetPasswordCode({
|
async verifyResetPasswordCode({
|
||||||
@@ -145,4 +157,15 @@ export class AuthClientService implements OnModuleInit {
|
|||||||
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
|
this.authService.AuthVerifyTotpMfa({ accessToken, totp }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSession(session: string) {
|
||||||
|
return lastValueFrom(this.authService.AuthGetSession({ session }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async oauthSignIn(data: { username: string; token: string }) {
|
||||||
|
const { username, token } = data;
|
||||||
|
return lastValueFrom(
|
||||||
|
this.authService.AuthOauthSignIn({ token, username, refreshToken: '' }),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
|
||||||
|
import {
|
||||||
|
AuthSignInRequest,
|
||||||
|
AuthSignInResponse,
|
||||||
|
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class LoginDto {
|
export class LoginDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -10,3 +15,106 @@ export class LoginDto {
|
|||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
totp: string;
|
totp: string;
|
||||||
}
|
}
|
||||||
|
export class AuthTokens {
|
||||||
|
@ApiProperty()
|
||||||
|
accessToken: string;
|
||||||
|
@ApiProperty()
|
||||||
|
refreshToken: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
termsOfUseToken: string;
|
||||||
|
@ApiProperty({ deprecated: true })
|
||||||
|
idToken: string;
|
||||||
|
}
|
||||||
|
export enum MfaStatuses {
|
||||||
|
PENDING = 'pending',
|
||||||
|
NONE = 'none',
|
||||||
|
TOTP = 'totp',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TouStatuses {
|
||||||
|
PENDING = 'pending',
|
||||||
|
REQUIRED = 'required',
|
||||||
|
OK = 'ok',
|
||||||
|
}
|
||||||
|
export class TermsOfUse {
|
||||||
|
@ApiProperty()
|
||||||
|
version: number;
|
||||||
|
@ApiProperty()
|
||||||
|
publicUrl: string;
|
||||||
|
@ApiProperty()
|
||||||
|
enforceDate: string;
|
||||||
|
@ApiProperty()
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
export class TermsOfUseStatus {
|
||||||
|
@ApiProperty({ enum: TouStatuses })
|
||||||
|
status: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
lastSigned?: TermsOfUse;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
next?: TermsOfUse;
|
||||||
|
}
|
||||||
|
export class AuthUser {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
username: string;
|
||||||
|
@ApiProperty()
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
export class AuthCustomer {
|
||||||
|
@ApiProperty()
|
||||||
|
modules: string[];
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
tier: string;
|
||||||
|
@ApiProperty()
|
||||||
|
scheduleLimit: string;
|
||||||
|
@ApiProperty()
|
||||||
|
links: Link[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthSignInReq implements AuthSignInRequest {
|
||||||
|
@ApiProperty()
|
||||||
|
username: string;
|
||||||
|
@ApiProperty()
|
||||||
|
password: string;
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'TOTP code used to login when Multi-Factor Authentication is enabled',
|
||||||
|
})
|
||||||
|
totp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthSignInRes implements AuthSignInResponse {
|
||||||
|
@ApiProperty()
|
||||||
|
permissions: string[];
|
||||||
|
@ApiProperty({ enum: MfaStatuses })
|
||||||
|
mfaStatus: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
customer?: AuthCustomer;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
user?: AuthUser;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
tokens?: AuthTokens;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
termsOfUse?: TermsOfUseStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthRefreshAccessTokenReq {
|
||||||
|
@ApiProperty()
|
||||||
|
refreshToken: string;
|
||||||
|
@ApiProperty()
|
||||||
|
customerName: string;
|
||||||
|
}
|
||||||
|
export class AuthRefreshAccessTokenRes {
|
||||||
|
@ApiProperty()
|
||||||
|
permissions: string[];
|
||||||
|
@ApiProperty()
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
AuthenticateOptionsGoogle,
|
||||||
|
Profile,
|
||||||
|
Strategy,
|
||||||
|
StrategyOptions,
|
||||||
|
} from 'passport-google-oauth20';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { OauthSecrets } from 'src/utils/OauthSecrets';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GoogleLoginStrategy extends PassportStrategy(
|
||||||
|
Strategy,
|
||||||
|
'google-login',
|
||||||
|
) {
|
||||||
|
redirect_uri: string;
|
||||||
|
constructor(
|
||||||
|
@Inject('OAUTH_SECRETS')
|
||||||
|
private readonly oauthSecrets: OauthSecrets,
|
||||||
|
) {
|
||||||
|
const options: StrategyOptions = {
|
||||||
|
clientID: oauthSecrets['google-login'].client_id,
|
||||||
|
clientSecret: oauthSecrets['google-login'].client_secret,
|
||||||
|
callbackURL: oauthSecrets['google-login'].redirect_uri,
|
||||||
|
scope: ['email', 'profile', 'openid'],
|
||||||
|
};
|
||||||
|
const verify = (
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string,
|
||||||
|
profile: Profile,
|
||||||
|
done,
|
||||||
|
) => {
|
||||||
|
return done(null, profile, { accessToken, refreshToken });
|
||||||
|
};
|
||||||
|
|
||||||
|
super(options, verify);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(req: Request, options: AuthenticateOptionsGoogle) {
|
||||||
|
const language = req.headers['dadosfera-lang'] || req.query.language;
|
||||||
|
options.state = jwt.sign({ language }, process.env.JWT_PRIVATE_KEY);
|
||||||
|
super.authenticate(req, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
ClientsProviderAsyncOptions,
|
||||||
|
GrpcOptions,
|
||||||
|
Transport,
|
||||||
|
} from '@nestjs/microservices';
|
||||||
|
import { credentials } from '@grpc/grpc-js';
|
||||||
|
import { Catalog } from '@dadosfera/protospack-v2';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
|
||||||
|
process.env.PIFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
|
export class CatalogClientConfiguration {
|
||||||
|
public name = 'CatalogClientConfiguration';
|
||||||
|
private config: GrpcOptions = {
|
||||||
|
transport: Transport.GRPC,
|
||||||
|
options: {
|
||||||
|
url: process.env.PIFACTORY_URL,
|
||||||
|
package: [
|
||||||
|
Catalog.ProtoPackages.ReadPackage,
|
||||||
|
Catalog.ProtoPackages.WritePackage,
|
||||||
|
],
|
||||||
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
|
protoPath: [
|
||||||
|
Catalog.ProtoPaths.ReadFilePath,
|
||||||
|
Catalog.ProtoPaths.WriteFilePath,
|
||||||
|
],
|
||||||
|
loader: {
|
||||||
|
keepCase: true,
|
||||||
|
enums: String,
|
||||||
|
defaults: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
providerOptions: ClientsProviderAsyncOptions = {
|
||||||
|
name: this.name,
|
||||||
|
...this.config,
|
||||||
|
};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,23 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
|||||||
|
|
||||||
import { CatalogController } from './catalog.controller';
|
import { CatalogController } from './catalog.controller';
|
||||||
import { CatalogService } from './catalog.service';
|
import { CatalogService } from './catalog.service';
|
||||||
|
import { CatalogClientConfiguration } from './catalog-client';
|
||||||
|
import { ClientsModule } from '@nestjs/microservices';
|
||||||
|
import { PipelinesModule as OldPipelineModule } from 'src/modules/pipelines/pipelines.module';
|
||||||
|
import { UsersModule } from '../users/users.module';
|
||||||
|
import { RolesModule } from '../roles/roles.module';
|
||||||
|
|
||||||
|
const client = new CatalogClientConfiguration();
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ClientsModule.register([client.providerOptions]),
|
||||||
|
OldPipelineModule,
|
||||||
|
UsersModule,
|
||||||
|
RolesModule,
|
||||||
|
],
|
||||||
controllers: [CatalogController],
|
controllers: [CatalogController],
|
||||||
providers: [CatalogService, DadosferaLogger],
|
providers: [CatalogService, DadosferaLogger],
|
||||||
|
exports: [CatalogService],
|
||||||
})
|
})
|
||||||
export class CatalogModule {}
|
export class CatalogModule {}
|
||||||
|
|||||||
@@ -1,6 +1,61 @@
|
|||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
||||||
|
import {
|
||||||
|
WriteService,
|
||||||
|
ReadService,
|
||||||
|
ProtoServices,
|
||||||
|
Messages,
|
||||||
|
} from '@dadosfera/protospack-v2/dist/lib/Catalog';
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Inject,
|
||||||
|
OnModuleInit,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { CatalogClientConfiguration } from './catalog-client';
|
||||||
|
import { UsersService } from '../users/users.service';
|
||||||
|
import { RolesService } from '../roles/roles.service';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
import {
|
||||||
|
BatchRemoveRlsRulesRequest,
|
||||||
|
IUpdateDataRequest,
|
||||||
|
TriggerCatalogReq,
|
||||||
|
} from './dtos';
|
||||||
|
import {
|
||||||
|
AddRlsRuleRequest,
|
||||||
|
GetNimbusDashboardsRequest,
|
||||||
|
GetRlsRulesRequest,
|
||||||
|
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
||||||
|
|
||||||
|
class CatalogService implements OnModuleInit {
|
||||||
|
catalogReadService: ReadService.CatalogReadServices;
|
||||||
|
catalogWriteService: WriteService.CatalogWriteServices;
|
||||||
|
logger: any;
|
||||||
|
constructor(
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
dadosferaLogger: DadosferaLogger,
|
||||||
|
@Inject(CatalogClientConfiguration.name)
|
||||||
|
private readonly grpcClient: ClientGrpc,
|
||||||
|
private readonly userService: UsersService,
|
||||||
|
private readonly roleService: RolesService,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
this.catalogReadService =
|
||||||
|
this.grpcClient.getService<ReadService.CatalogReadServices>(
|
||||||
|
ProtoServices.CatalogReadServices,
|
||||||
|
);
|
||||||
|
this.catalogWriteService =
|
||||||
|
this.grpcClient.getService<WriteService.CatalogWriteServices>(
|
||||||
|
ProtoServices.CatalogWriteServices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class CatalogService {
|
|
||||||
_getNimbusUrl(body) {
|
_getNimbusUrl(body) {
|
||||||
const customer = body.info.customer.toLowerCase();
|
const customer = body.info.customer.toLowerCase();
|
||||||
|
|
||||||
@@ -8,138 +63,228 @@ class CatalogService {
|
|||||||
return `https://nimbus-${customer}.dadosfera.ai`;
|
return `https://nimbus-${customer}.dadosfera.ai`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `https://nimbus-${customer}.${process.env.ENV}.dadosfera.ai`;
|
return `https://nimbus-${customer}.${process.env.ENV.replace(
|
||||||
|
'local',
|
||||||
|
'stg',
|
||||||
|
)}.dadosfera.ai`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async catalogAll(body) {
|
async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/all/`);
|
if (!data.embed) data.embed = undefined;
|
||||||
|
|
||||||
return data;
|
return lastValueFrom(
|
||||||
}
|
this.catalogWriteService.CreateDataAsset(data, metadata),
|
||||||
|
|
||||||
async dataAppsAll(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data_apps/`);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async dataAppsOne(id, body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/data_apps/${id}/`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllDashboardMetabase(body) {
|
async managePermissions(data: Messages.ManagePermissionRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/dashboard-metabase/`,
|
return lastValueFrom(
|
||||||
|
this.catalogWriteService.ManagePermission(data, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOneDashboardMetabase(id, body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/dashboard-metabase/${id}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// const [, path] = data.iframe_url.split('.dadosfera');
|
|
||||||
// const host = `httpss://metabase-${body.info.customer.toLowerCase()}.dadosfera.ai`;
|
|
||||||
// data.iframe_url = host + path;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllTableMetadata(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/table-metadata/`,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOneTableMetadata(body, params) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/table-metadata/`,
|
|
||||||
{ params: params },
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteOneTableMetadata(id, body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.delete(
|
|
||||||
`${nimbusUrl}/api/catalog/table-metadata/${id}`,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOneColumnMetadata(body, params) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/column-metadata/`,
|
|
||||||
{ params: params },
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteOneColumnMetadata(id, body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.delete(
|
|
||||||
`${nimbusUrl}/api/catalog/column-metadata/${id}/`,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOneDataPreview(body, params) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-preview/`, {
|
|
||||||
params: params,
|
|
||||||
});
|
});
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDataStatus(body) {
|
async revokePermissions(data: Messages.RevokePermissionRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-status/`);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDataStatus(body) {
|
return lastValueFrom(
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.catalogWriteService.RevokePermission(data, metadata),
|
||||||
const { data } = await axios.post(
|
).catch((err) => {
|
||||||
`${nimbusUrl}/api/catalog/data-status/`,
|
throw new HttpException(
|
||||||
body,
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||||
);
|
);
|
||||||
return data;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDataDescription(body) {
|
async commentOnDataAsset(data: Messages.MakeACommentRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/data-description/`,
|
return lastValueFrom(
|
||||||
|
this.catalogWriteService.MakeAComment(data, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||||
);
|
);
|
||||||
return data;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDataDescription(body) {
|
async deleteComment(data: Messages.UpdateACommentRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.post(
|
|
||||||
`${nimbusUrl}/api/catalog/data-description/`,
|
return lastValueFrom(
|
||||||
body,
|
this.catalogWriteService.UpdateAComment(data, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||||
);
|
);
|
||||||
return data;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDataDocs(body) {
|
async deleteDataAsset(data: Messages.DeleteDataAssetRequest, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-docs/`);
|
|
||||||
return data;
|
return lastValueFrom(
|
||||||
|
this.catalogWriteService.DeleteDataAsset(data, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserRolesIds(userId: string) {
|
||||||
|
const result = await this.userService.findOneById(userId).catch(() => null);
|
||||||
|
|
||||||
|
const roles_ids = result.user.roles.map((role) => role.id);
|
||||||
|
|
||||||
|
return roles_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchDataAssets(
|
||||||
|
query: Record<string, any>,
|
||||||
|
metadata: Metadata,
|
||||||
|
customer_id: string,
|
||||||
|
) {
|
||||||
|
this.logger.info('CatalogService - searchDataAssets');
|
||||||
|
|
||||||
|
const { search, page, size, sort_by, order, ...filters } = query;
|
||||||
|
|
||||||
|
const { data_assets, total } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetAllDataAssets(
|
||||||
|
{
|
||||||
|
search,
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
sort_by,
|
||||||
|
order,
|
||||||
|
filters: JSON.stringify(filters),
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = JSON.parse(data_assets);
|
||||||
|
|
||||||
|
const response = await this.getAssetsUsersAndRoles(
|
||||||
|
result.data_assets,
|
||||||
|
customer_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data_assets: response, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneDataAsset(data: {
|
||||||
|
id: string;
|
||||||
|
customer_id: string;
|
||||||
|
metadata: Metadata;
|
||||||
|
}) {
|
||||||
|
const { customer_id, id, metadata } = data;
|
||||||
|
const { data_asset } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetOneDataAsset(
|
||||||
|
{ id, type: undefined },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let asset = JSON.parse(data_asset);
|
||||||
|
asset = {
|
||||||
|
...asset,
|
||||||
|
p_roles: asset.roles,
|
||||||
|
p_users: asset.users,
|
||||||
|
};
|
||||||
|
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
|
||||||
|
|
||||||
|
return { data_asset: asset[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneDataAssetByPipelineAndObject(data: {
|
||||||
|
customer_id: string;
|
||||||
|
pipeline: string;
|
||||||
|
object: string;
|
||||||
|
metadata: Metadata;
|
||||||
|
}) {
|
||||||
|
const { customer_id, metadata, pipeline, object } = data;
|
||||||
|
const { data_asset } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetOneDataAssetByPipelineAndObject(
|
||||||
|
{ pipeline, object },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let asset = JSON.parse(data_asset);
|
||||||
|
asset = {
|
||||||
|
...asset,
|
||||||
|
p_roles: asset.roles,
|
||||||
|
p_users: asset.users,
|
||||||
|
};
|
||||||
|
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
|
||||||
|
|
||||||
|
return { data_asset: asset[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOneDataAsset(data: {
|
||||||
|
data_asset_id: string;
|
||||||
|
customer_id: string;
|
||||||
|
body: IUpdateDataRequest;
|
||||||
|
metadata: Metadata;
|
||||||
|
}) {
|
||||||
|
const { body, customer_id, data_asset_id, metadata } = data;
|
||||||
|
|
||||||
|
const { data_asset } = await lastValueFrom(
|
||||||
|
this.catalogWriteService.UpdateDataAsset(
|
||||||
|
{ id: data_asset_id, changes: JSON.stringify(body) },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let asset = JSON.parse(data_asset);
|
||||||
|
asset = {
|
||||||
|
...asset,
|
||||||
|
p_roles: asset.roles,
|
||||||
|
p_users: asset.users,
|
||||||
|
};
|
||||||
|
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
|
||||||
|
|
||||||
|
return { data_asset: asset[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDataDocs(id: string, metadata: Metadata) {
|
||||||
|
const { documentation } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata),
|
||||||
|
);
|
||||||
|
console.log(documentation);
|
||||||
|
const docs = JSON.parse(documentation);
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatasetPreview(id: string, metadata: Metadata) {
|
||||||
|
const { preview } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetDatasetPreview(
|
||||||
|
{ id, type: undefined },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const result = JSON.parse(preview);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatasetColumnsMetadata(id: string, metadata: Metadata) {
|
||||||
|
const { columns_metadata } = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetDatasetColumnsMetadata(
|
||||||
|
{ id, type: undefined },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const result = JSON.parse(columns_metadata);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDataDocs(body) {
|
async createDataDocs(body) {
|
||||||
@@ -151,111 +296,129 @@ class CatalogService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDataRating(body) {
|
async findAllTags(data, metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.logger.info('CatalogService - findAllCustomerTags');
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-rating/`);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDataRating(body) {
|
const response = await lastValueFrom(
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
this.catalogReadService.GetCustomerTags(data, metadata),
|
||||||
const { data } = await axios.post(
|
)
|
||||||
`${nimbusUrl}/api/catalog/data-rating/`,
|
.then((res) => {
|
||||||
body,
|
this.logger.info('Done');
|
||||||
);
|
return res;
|
||||||
return data;
|
})
|
||||||
}
|
.catch((err) => {
|
||||||
|
this.logger.error(err.message);
|
||||||
async getSummaryRating(id, body) {
|
throw new Error(err);
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/summary-rating/${id}`,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataComment(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-comment/`);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDataComment(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.post(
|
|
||||||
`${nimbusUrl}/api/catalog/data-comment/`,
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataReview(body, params) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-review/`, {
|
|
||||||
params: params,
|
|
||||||
});
|
});
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createTags(body) {
|
return response;
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.post(`${nimbusUrl}/api/catalog/tags/`, body);
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
async getAssetsUsersAndRoles(data_assets: Array<any>, customer_id: string) {
|
||||||
async findAllTags(body) {
|
const { users: customer_users } =
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
await this.userService.findAllUsersByCustomerId(customer_id);
|
||||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/tags/`, body);
|
const { roles: customer_roles } = await this.roleService.roleSearch(
|
||||||
return data;
|
{},
|
||||||
}
|
{ customer_id },
|
||||||
|
|
||||||
async findAllTableTags(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.get(
|
|
||||||
`${nimbusUrl}/api/catalog/table-tags/`,
|
|
||||||
body,
|
|
||||||
);
|
);
|
||||||
return data;
|
return data_assets.map((data_asset) => {
|
||||||
|
const owner = customer_users.find(
|
||||||
|
(u) => u.id === data_asset.owner,
|
||||||
|
)?.username;
|
||||||
|
|
||||||
|
const roles = [];
|
||||||
|
const users = [];
|
||||||
|
for (const role_id of data_asset.roles) {
|
||||||
|
const role = customer_roles.find((r) => r.id === role_id);
|
||||||
|
if (role) roles.push({ id: role.id, name: role.name });
|
||||||
|
}
|
||||||
|
for (const user_id of data_asset.users) {
|
||||||
|
const user = customer_users.find((r) => r.id === user_id);
|
||||||
|
if (user) users.push({ id: user.id, username: user.username });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...data_asset,
|
||||||
|
roles,
|
||||||
|
users,
|
||||||
|
owner,
|
||||||
|
} as typeof data_asset;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTags(id, body) {
|
async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
const { session } = await lastValueFrom(
|
||||||
const { data } = await axios.delete(`${nimbusUrl}/api/catalog/tags/${id}`);
|
this.catalogWriteService.TriggerDatasetCataloging(data, metadata),
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createTableTags(body) {
|
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
|
||||||
const { data } = await axios.post(
|
|
||||||
`${nimbusUrl}/api/catalog/table-tags/`,
|
|
||||||
body,
|
|
||||||
);
|
);
|
||||||
return data;
|
return session;
|
||||||
|
}
|
||||||
|
async getDatasetCatalogTask(session: string, metadata: Metadata) {
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetDatasetCatalogTask({ session }, metadata),
|
||||||
|
);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTableTags(id, body) {
|
async addRlsRule(data: AddRlsRuleRequest, metadata: Metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
const res = await lastValueFrom(
|
||||||
const { data } = await axios.delete(
|
this.catalogWriteService.AddRlsRule(data, metadata),
|
||||||
`${nimbusUrl}/api/catalog/table-tags/${id}`,
|
|
||||||
);
|
);
|
||||||
return data;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTableRules(body) {
|
async removeRlsRule(id: number, metadata: Metadata) {
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
const res = await lastValueFrom(
|
||||||
const { data } = await axios.get(
|
this.catalogWriteService.RemoveRlsRule({ id }, metadata),
|
||||||
`${nimbusUrl}/api/catalog/table-rules/`,
|
|
||||||
body,
|
|
||||||
);
|
);
|
||||||
return data;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTableRules(id, body) {
|
async batchRemoveRlsRule(
|
||||||
const nimbusUrl = this._getNimbusUrl(body);
|
query: BatchRemoveRlsRulesRequest,
|
||||||
const { data } = await axios.delete(
|
metadata: Metadata,
|
||||||
`${nimbusUrl}/api/catalog/table-rules/${id}`,
|
) {
|
||||||
|
const { id_rls, nimbus_dashboard_id } = query;
|
||||||
|
|
||||||
|
if (id_rls && nimbus_dashboard_id) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"You can't delete using both parameters. Choose either 'id_rls' or 'nimbus_dashboard_id'",
|
||||||
);
|
);
|
||||||
return data;
|
}
|
||||||
|
if (id_rls) {
|
||||||
|
await lastValueFrom(
|
||||||
|
this.catalogWriteService.RemoveRlsRulesByRlsId({ id_rls }, metadata),
|
||||||
|
);
|
||||||
|
} else if (nimbus_dashboard_id) {
|
||||||
|
await lastValueFrom(
|
||||||
|
this.catalogWriteService.RemoveRlsRulesByDashboardId(
|
||||||
|
{ nimbus_dashboard_id: parseInt(nimbus_dashboard_id) },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRlsRules(data: GetRlsRulesRequest, metadata: Metadata) {
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetRlsRules(data, metadata),
|
||||||
|
);
|
||||||
|
return res.rls_rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOneRlsRule(id: number, metadata: Metadata) {
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetOneRlsRule({ id }, metadata),
|
||||||
|
);
|
||||||
|
return res.rls_rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNimbusDashboards(
|
||||||
|
data: GetNimbusDashboardsRequest,
|
||||||
|
metadata: Metadata,
|
||||||
|
) {
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.catalogReadService.GetNimbusDashboards(data, metadata),
|
||||||
|
);
|
||||||
|
return res.dashboards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,322 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger';
|
||||||
|
import { CreateDataAssetRequest } from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
||||||
|
|
||||||
|
export enum DataAssetShareType {
|
||||||
|
none = 'none',
|
||||||
|
public = 'public',
|
||||||
|
private = 'private',
|
||||||
|
}
|
||||||
|
export enum OrderEnum {
|
||||||
|
asc = 'asc',
|
||||||
|
desc = 'desc',
|
||||||
|
}
|
||||||
|
export class EmbedObject {
|
||||||
|
@ApiProperty()
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
export class IRole {
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IUser {
|
||||||
|
@ApiProperty()
|
||||||
|
username: string;
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IComment {
|
||||||
|
@ApiProperty()
|
||||||
|
id: number;
|
||||||
|
@ApiProperty()
|
||||||
|
message: string;
|
||||||
|
@ApiProperty()
|
||||||
|
created_at: string;
|
||||||
|
@ApiProperty()
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IDataAsset {
|
||||||
|
@ApiProperty()
|
||||||
|
id: number;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
database_name?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
table_name?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
num_columns?: number;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
location?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
created_time?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
table_type?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
size_in_bytes?: number;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
num_files?: number;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
data_encrypted?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
num_rows?: number;
|
||||||
|
@ApiProperty()
|
||||||
|
display_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
pipeline_id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
user_id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
@ApiProperty()
|
||||||
|
data_asset_type: string;
|
||||||
|
@ApiProperty()
|
||||||
|
tags: string[];
|
||||||
|
@ApiProperty()
|
||||||
|
owner: string;
|
||||||
|
@ApiProperty({ type: [IRole] })
|
||||||
|
roles: IRole[];
|
||||||
|
@ApiProperty({ type: [IUser] })
|
||||||
|
users: IUser[];
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
dashboard_id?: number;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
name?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
parameters?: string[];
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
embedding_params?: object;
|
||||||
|
@ApiProperty()
|
||||||
|
created_at: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
updated_at?: string;
|
||||||
|
@ApiProperty({ type: [IComment] })
|
||||||
|
comments: IComment[];
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
embed?: EmbedObject;
|
||||||
|
@ApiPropertyOptional({ enum: DataAssetShareType })
|
||||||
|
share_type?: DataAssetShareType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IOneDataAsset {
|
||||||
|
@ApiProperty()
|
||||||
|
data_asset: IDataAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IMakeAComment {
|
||||||
|
@ApiProperty()
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommentToDelete extends PickType(IComment, ['id', 'username']) {}
|
||||||
|
export class IDeleteComment {
|
||||||
|
@ApiProperty()
|
||||||
|
comment: CommentToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ICatalogAllRequest {
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Texto usado para filtrar os ativos no catálogo.',
|
||||||
|
})
|
||||||
|
search;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Número de registros a ser retornado',
|
||||||
|
maximum: 10000,
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
size: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Usado para paginação, em conjunto com `size`',
|
||||||
|
minimum: 1,
|
||||||
|
})
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'Campo do data asset para usar na ordenação. Padrão: `created_at` ',
|
||||||
|
})
|
||||||
|
sort_by: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
enum: OrderEnum,
|
||||||
|
default: OrderEnum.asc,
|
||||||
|
description: 'Tipo de ordenação - `asc`: crescente; `desc`: decrescente ',
|
||||||
|
})
|
||||||
|
order?: OrderEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ICatalogAllResponse {
|
||||||
|
@ApiProperty({ type: [IDataAsset] })
|
||||||
|
data_assets: IDataAsset[];
|
||||||
|
@ApiProperty()
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IData {
|
||||||
|
@ApiProperty()
|
||||||
|
var: number;
|
||||||
|
@ApiProperty()
|
||||||
|
date: string;
|
||||||
|
@ApiProperty()
|
||||||
|
last: number;
|
||||||
|
@ApiProperty()
|
||||||
|
maximum: number;
|
||||||
|
@ApiProperty()
|
||||||
|
minimum: number;
|
||||||
|
@ApiProperty()
|
||||||
|
day_opening: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IUpdateDataRequest {
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
@ApiProperty()
|
||||||
|
tags: string[];
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
embed: EmbedObject;
|
||||||
|
@ApiPropertyOptional({ enum: DataAssetShareType })
|
||||||
|
share_type?: DataAssetShareType;
|
||||||
|
}
|
||||||
|
export class ICreateDataAsset implements CreateDataAssetRequest {
|
||||||
|
@ApiProperty()
|
||||||
|
display_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
@ApiProperty()
|
||||||
|
data_asset_type: string;
|
||||||
|
@ApiProperty()
|
||||||
|
external_url: string;
|
||||||
|
@ApiProperty()
|
||||||
|
location: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
embed: EmbedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IPreview {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
database_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
table_name: string;
|
||||||
|
@ApiProperty({ type: [IData] })
|
||||||
|
data: IData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IDocs {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
docs: string;
|
||||||
|
@ApiProperty()
|
||||||
|
updated_at: string;
|
||||||
|
@ApiProperty()
|
||||||
|
table_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IColumnMetadata {
|
||||||
|
@ApiProperty()
|
||||||
|
id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
table_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
column_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
data_type: string;
|
||||||
|
@ApiProperty()
|
||||||
|
missing_count: string;
|
||||||
|
@ApiProperty()
|
||||||
|
min: string;
|
||||||
|
@ApiProperty()
|
||||||
|
max: string;
|
||||||
|
@ApiProperty()
|
||||||
|
count: string;
|
||||||
|
@ApiProperty()
|
||||||
|
histogram: string;
|
||||||
|
@ApiProperty()
|
||||||
|
mean: string;
|
||||||
|
@ApiProperty()
|
||||||
|
median: string;
|
||||||
|
@ApiProperty()
|
||||||
|
missing_rate: string;
|
||||||
|
@ApiProperty()
|
||||||
|
stddev: string;
|
||||||
|
@ApiProperty()
|
||||||
|
unique_count: string;
|
||||||
|
@ApiProperty()
|
||||||
|
unique_rate: string;
|
||||||
|
@ApiProperty()
|
||||||
|
analyzer_rules: string[];
|
||||||
|
@ApiProperty()
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IPreviewResponse {
|
||||||
|
@ApiProperty()
|
||||||
|
preview: IPreview;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IDocsResponse {
|
||||||
|
@ApiProperty()
|
||||||
|
docs: IDocs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IColumnsMetadataResponse {
|
||||||
|
@ApiProperty({ type: [IColumnMetadata] })
|
||||||
|
columns_metadata: IColumnMetadata[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TriggerCatalogReq {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Name of the table on Snowflake',
|
||||||
|
example: 'TB__4DXSD4_TEST',
|
||||||
|
})
|
||||||
|
table_name: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'Schema where the asset is located. If not set defaults to "PUBLIC"',
|
||||||
|
})
|
||||||
|
table_schema?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Id of the pipeline that generated the asset.',
|
||||||
|
})
|
||||||
|
pipeline_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TriggerCatalogRes {
|
||||||
|
@ApiProperty()
|
||||||
|
session: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetDatasetCatalogTaskRes {
|
||||||
|
@ApiProperty()
|
||||||
|
created_at: string;
|
||||||
|
@ApiProperty()
|
||||||
|
customer_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
session_id: string;
|
||||||
|
@ApiProperty()
|
||||||
|
status: string;
|
||||||
|
@ApiProperty()
|
||||||
|
table_name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
updated_at: string;
|
||||||
|
@ApiProperty()
|
||||||
|
updated_by: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BatchRemoveRlsRulesRequest {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
nimbus_dashboard_id?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
id_rls?: string;
|
||||||
|
}
|
||||||
@@ -6,14 +6,17 @@ import {
|
|||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
import { ConnectionTest } from '@dadosfera/protospack-v2';
|
import { ConnectionTest } from '@dadosfera/protospack-v2';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||||
|
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class ConnectionTestClientConfiguration {
|
export class ConnectionTestClientConfiguration {
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
transport: Transport.GRPC,
|
transport: Transport.GRPC,
|
||||||
options: {
|
options: {
|
||||||
url: process.env.INFACTORY_URL,
|
url: process.env.INFACTORY_URL,
|
||||||
package: [ConnectionTest.ProtoPackages.ReadPackage],
|
package: [ConnectionTest.ProtoPackages.ReadPackage],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.ENV === 'local' ? undefined : credentials.createSsl(),
|
|
||||||
protoPath: [ConnectionTest.ProtoPaths.ReadFilePath],
|
protoPath: [ConnectionTest.ProtoPaths.ReadFilePath],
|
||||||
loader: {
|
loader: {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
ValidationPipe,
|
ValidationPipe,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { User, RequestUser } from 'src/authentication/user.decorator';
|
import { User, RequestUser } from 'src/decorators/user.decorator';
|
||||||
import { ConnectionTestService } from './connection-test.service';
|
import { ConnectionTestService } from './connection-test.service';
|
||||||
import {
|
import {
|
||||||
ConnectionTestPingRes,
|
ConnectionTestPingRes,
|
||||||
@@ -24,9 +24,11 @@ import {
|
|||||||
GetTableMetadataReq,
|
GetTableMetadataReq,
|
||||||
} from './dto/connection-test';
|
} from './dto/connection-test';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { Authenticated } from 'src/authentication/authentication.decorator';
|
import { Authenticated } from 'src/decorators/authentication.decorator';
|
||||||
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
|
@ApiInternalOnlyController()
|
||||||
@ApiTags('Connection Test')
|
@ApiTags('Connection Test')
|
||||||
@Controller('connection-test')
|
@Controller('connection-test')
|
||||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
CreateConnectionDto,
|
CreateConnectionDto,
|
||||||
DatabaseConnectionPropertiesDto,
|
DatabaseConnectionPropertiesDto,
|
||||||
} from '../connection/dtos/connection';
|
} from '../connection/dtos/connection';
|
||||||
import { RequestUser } from 'src/authentication/user.decorator';
|
import { RequestUser } from 'src/decorators/user.decorator';
|
||||||
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
import { ConnectionManager } from '@dadosfera/protospack-v2';
|
import { ConnectionManager } from '@dadosfera/protospack-v2';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||||
|
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class ConnectionClientConfiguration {
|
export class ConnectionClientConfiguration {
|
||||||
public name = 'ConnectionClientConfiguration';
|
public name = 'ConnectionClientConfiguration';
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
@@ -16,10 +20,7 @@ export class ConnectionClientConfiguration {
|
|||||||
ConnectionManager.ProtoPackages.WritePackage,
|
ConnectionManager.ProtoPackages.WritePackage,
|
||||||
ConnectionManager.ProtoPackages.ReadPackage,
|
ConnectionManager.ProtoPackages.ReadPackage,
|
||||||
],
|
],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: [
|
protoPath: [
|
||||||
ConnectionManager.ProtoPaths.WriteFilePath,
|
ConnectionManager.ProtoPaths.WriteFilePath,
|
||||||
ConnectionManager.ProtoPaths.ReadFilePath,
|
ConnectionManager.ProtoPaths.ReadFilePath,
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { OnModuleInit, Inject } from '@nestjs/common';
|
||||||
OnModuleInit,
|
|
||||||
Inject,
|
|
||||||
HttpException,
|
|
||||||
HttpStatus,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ClientGrpc } from '@nestjs/microservices';
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
import { ConnectionManager } from '@dadosfera/protospack-v2';
|
import { ConnectionManager } from '@dadosfera/protospack-v2';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
@@ -86,12 +81,7 @@ export class ConnectionClientService implements OnModuleInit {
|
|||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
),
|
),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateConnection(
|
async updateConnection(
|
||||||
@@ -112,12 +102,7 @@ export class ConnectionClientService implements OnModuleInit {
|
|||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
),
|
),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteConnection({ body, metadata }): Promise<ConnectionRes> {
|
async deleteConnection({ body, metadata }): Promise<ConnectionRes> {
|
||||||
@@ -130,12 +115,7 @@ export class ConnectionClientService implements OnModuleInit {
|
|||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
),
|
),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllConnections({ body, metadata }): Promise<ConnectionsRes> {
|
async getAllConnections({ body, metadata }): Promise<ConnectionsRes> {
|
||||||
@@ -146,12 +126,7 @@ export class ConnectionClientService implements OnModuleInit {
|
|||||||
{ search, filters: JSON.stringify(filters), size, page },
|
{ search, filters: JSON.stringify(filters), size, page },
|
||||||
metadata,
|
metadata,
|
||||||
),
|
),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConnectionDetails({
|
async getConnectionDetails({
|
||||||
@@ -162,12 +137,7 @@ export class ConnectionClientService implements OnModuleInit {
|
|||||||
|
|
||||||
return lastValueFrom(
|
return lastValueFrom(
|
||||||
this.connectionServiceRead.GetConnectionDetails({ id }, metadata),
|
this.connectionServiceRead.GetConnectionDetails({ id }, metadata),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async INTERNAL_getAllConnections({
|
async INTERNAL_getAllConnections({
|
||||||
|
|||||||
@@ -7,31 +7,36 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
Headers,
|
|
||||||
Query,
|
Query,
|
||||||
|
UseFilters,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||||
import { ConnectionClientService } from './client.service';
|
import { ConnectionClientService } from './client.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import {
|
import {
|
||||||
Authenticated,
|
Authenticated,
|
||||||
RequireAllPermissions,
|
RequireAllPermissions,
|
||||||
} from 'src/authentication/authentication.decorator';
|
} from 'src/decorators/authentication.decorator';
|
||||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
import { ValidationPipe } from '../../pipes/object-validation.pipe';
|
import { ValidationPipe } from '../../pipes/object-validation.pipe';
|
||||||
import {
|
import {
|
||||||
ConnectionDetailsRes,
|
ConnectionDetailsRes,
|
||||||
ConnectionRes,
|
ConnectionRes,
|
||||||
ConnectionsRes,
|
ConnectionsRes,
|
||||||
|
GetAllConnectionsReq,
|
||||||
UpdateConnectionDto,
|
UpdateConnectionDto,
|
||||||
} from './dtos/connection';
|
} from './dtos/connection';
|
||||||
import { CreateConnectionDto } from './dtos/connection';
|
import { CreateConnectionDto } from './dtos/connection';
|
||||||
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
const connectionPermissions = PERMISSIONS_GROUPS.CONNECTION.permissions;
|
const connectionPermissions = PERMISSIONS_GROUPS.CONNECTION.permissions;
|
||||||
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
@ApiTags('connections')
|
@ApiTags('connections')
|
||||||
@ApiBearerAuth()
|
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
@Controller('connections')
|
@Controller('connections')
|
||||||
export class ConnectionController {
|
export class ConnectionController {
|
||||||
@@ -44,8 +49,12 @@ export class ConnectionController {
|
|||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('connections-migration')
|
@Get('connections-migration')
|
||||||
@RequireAllPermissions(PERMISSIONS_GROUPS.DADOSFERA.permissions.ADMIN)
|
@ApiExcludeEndpoint()
|
||||||
|
@RequireAllPermissions(
|
||||||
|
PERMISSIONS_GROUPS.DADOSFERA.permissions.CONNECTIONS_MIGRATION,
|
||||||
|
)
|
||||||
async synchronizeConnections(@Query('apply') apply: string) {
|
async synchronizeConnections(@Query('apply') apply: string) {
|
||||||
return this.clientService.synchronizeConnections(apply === 'true');
|
return this.clientService.synchronizeConnections(apply === 'true');
|
||||||
}
|
}
|
||||||
@@ -78,12 +87,11 @@ export class ConnectionController {
|
|||||||
@RequireAllPermissions(connectionPermissions.CREATE)
|
@RequireAllPermissions(connectionPermissions.CREATE)
|
||||||
async updateConnection(
|
async updateConnection(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('Dadosfera-Lang') language: string,
|
@Language() language: LanguageEnum,
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body(new ValidationPipe()) body: UpdateConnectionDto,
|
@Body(new ValidationPipe()) body: UpdateConnectionDto,
|
||||||
): Promise<ConnectionRes> {
|
): Promise<ConnectionRes> {
|
||||||
this.logger.info('/connections - Update Connection');
|
this.logger.info('/connections - Update Connection');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { user_id, customer_id, customer_name, username } = user;
|
const { user_id, customer_id, customer_name, username } = user;
|
||||||
const metadata = PackTheMetadata({
|
const metadata = PackTheMetadata({
|
||||||
@@ -103,38 +111,37 @@ export class ConnectionController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Delete('/:id')
|
@Delete('/:id')
|
||||||
// @RequireAllPermissions(connectionPermissions.DELETE)
|
@RequireAllPermissions(connectionPermissions.DELETE)
|
||||||
// async deleteConnection(
|
async deleteConnection(
|
||||||
// @User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
// @Param('id') id: string,
|
@Param('id') id: string,
|
||||||
// ): Promise<ConnectionRes> {
|
): Promise<ConnectionRes> {
|
||||||
// this.logger.info('/connections - Delete Connection');
|
this.logger.info('/connections - Delete Connection');
|
||||||
|
|
||||||
// const { user_id, customer_id, customer_name, username } = user;
|
const { user_id, customer_id, customer_name, username } = user;
|
||||||
// const metadata = PackTheMetadata({
|
const metadata = PackTheMetadata({
|
||||||
// user_id,
|
user_id,
|
||||||
// customer_id,
|
customer_id,
|
||||||
// customer_name,
|
customer_name,
|
||||||
// username,
|
username,
|
||||||
// });
|
});
|
||||||
|
|
||||||
// const response: any = await this.clientService.deleteConnection({
|
const response: any = await this.clientService.deleteConnection({
|
||||||
// body: { id },
|
body: { id },
|
||||||
// metadata,
|
metadata,
|
||||||
// });
|
});
|
||||||
|
|
||||||
// return response;
|
return response;
|
||||||
// }
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async getAllConnections(
|
async getAllConnections(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Query() queries,
|
@Query() queries: GetAllConnectionsReq,
|
||||||
): Promise<ConnectionsRes> {
|
): Promise<ConnectionsRes> {
|
||||||
this.logger.info('/connections - Get All Connection');
|
this.logger.info('/connections - Get All Connection');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { user_id, customer_id, customer_name, username } = user;
|
const { user_id, customer_id, customer_name, username } = user;
|
||||||
const { search, size, page, ...filters } = queries;
|
const { search, size, page, ...filters } = queries;
|
||||||
@@ -156,13 +163,16 @@ export class ConnectionController {
|
|||||||
|
|
||||||
@Get('/:id')
|
@Get('/:id')
|
||||||
async getConnectionDetails(
|
async getConnectionDetails(
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Param('id') id,
|
@Param('id') id: string,
|
||||||
@Query('details') details,
|
@Query() queries,
|
||||||
): Promise<ConnectionDetailsRes> {
|
): Promise<ConnectionDetailsRes> {
|
||||||
this.logger.info('/connections - Get Connection Details');
|
this.logger.info('/connections - Get Connection Details');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
console.log(queries);
|
||||||
|
|
||||||
|
const { details, sensitive } = queries;
|
||||||
|
|
||||||
const { user_id, customer_id, customer_name, username } = user;
|
const { user_id, customer_id, customer_name, username } = user;
|
||||||
const metadata = PackTheMetadata({
|
const metadata = PackTheMetadata({
|
||||||
@@ -172,6 +182,7 @@ export class ConnectionController {
|
|||||||
username,
|
username,
|
||||||
details,
|
details,
|
||||||
language,
|
language,
|
||||||
|
sensitive,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response: any = await this.clientService.getConnectionDetails({
|
const response: any = await this.clientService.getConnectionDetails({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ConnectionToCatalog,
|
ConnectionToCatalog,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities';
|
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities';
|
||||||
import {
|
import {
|
||||||
|
GetAllConnectionRequest,
|
||||||
GetAllConnectionResponse,
|
GetAllConnectionResponse,
|
||||||
GetConnectionDetailsResponse,
|
GetConnectionDetailsResponse,
|
||||||
GetConnectionResponse,
|
GetConnectionResponse,
|
||||||
@@ -21,7 +22,7 @@ export type ConnectionCredentialsType =
|
|||||||
| 'oauth'
|
| 'oauth'
|
||||||
| 'api_key'
|
| 'api_key'
|
||||||
| 'service_account'
|
| 'service_account'
|
||||||
| 'headers_authF';
|
| 'headers_auth';
|
||||||
export interface ConnectionApiConnection {
|
export interface ConnectionApiConnection {
|
||||||
config_id: string;
|
config_id: string;
|
||||||
plugin: string;
|
plugin: string;
|
||||||
@@ -94,7 +95,6 @@ export class ConnectionDto implements Connection {
|
|||||||
|
|
||||||
export class ConnectionToCatalogDto implements ConnectionToCatalog {
|
export class ConnectionToCatalogDto implements ConnectionToCatalog {
|
||||||
// ---Automatically generated information - will not be sent by the frontend--- //
|
// ---Automatically generated information - will not be sent by the frontend--- //
|
||||||
id: string;
|
|
||||||
customer_id: string;
|
customer_id: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -104,6 +104,9 @@ export class ConnectionToCatalogDto implements ConnectionToCatalog {
|
|||||||
customer_name: string;
|
customer_name: string;
|
||||||
|
|
||||||
// ---Information sent by the frontend--- //
|
// ---Information sent by the frontend--- //
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
id: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@@ -147,6 +150,15 @@ export class UpdateConnectionDto extends PickType(ConnectionDto, [
|
|||||||
'properties',
|
'properties',
|
||||||
]) {}
|
]) {}
|
||||||
|
|
||||||
|
export class GetAllConnectionsReq implements GetAllConnectionRequest {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
search?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
page?: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
size?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ConnectionsRes implements GetAllConnectionResponse {
|
export class ConnectionsRes implements GetAllConnectionResponse {
|
||||||
@ApiProperty({ type: [ConnectionToCatalogDto] })
|
@ApiProperty({ type: [ConnectionToCatalogDto] })
|
||||||
connections: ConnectionToCatalogDto[];
|
connections: ConnectionToCatalogDto[];
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
import { ConnectorManager } from '@dadosfera/protospack-v2';
|
import { ConnectorManager } from '@dadosfera/protospack-v2';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||||
|
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class ConnectorClientConfiguration {
|
export class ConnectorClientConfiguration {
|
||||||
public name = 'ConnectorClientConfiguration';
|
public name = 'ConnectorClientConfiguration';
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
@@ -16,10 +20,7 @@ export class ConnectorClientConfiguration {
|
|||||||
ConnectorManager.ProtoPackages.WritePackage,
|
ConnectorManager.ProtoPackages.WritePackage,
|
||||||
ConnectorManager.ProtoPackages.ReadPackage,
|
ConnectorManager.ProtoPackages.ReadPackage,
|
||||||
],
|
],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: [
|
protoPath: [
|
||||||
ConnectorManager.ProtoPaths.WriteFilePath,
|
ConnectorManager.ProtoPaths.WriteFilePath,
|
||||||
ConnectorManager.ProtoPaths.ReadFilePath,
|
ConnectorManager.ProtoPaths.ReadFilePath,
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export class ConnectorClientService implements OnModuleInit {
|
|||||||
return lastValueFrom(
|
return lastValueFrom(
|
||||||
this.connectorServiceWrite.RegisterConnector(serviceBody),
|
this.connectorServiceWrite.RegisterConnector(serviceBody),
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
console.log(err.details);
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
err.details,
|
err.details,
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||||
@@ -64,7 +63,6 @@ export class ConnectorClientService implements OnModuleInit {
|
|||||||
|
|
||||||
return lastValueFrom(this.connectorServiceWrite.UploadFile(body)).catch(
|
return lastValueFrom(this.connectorServiceWrite.UploadFile(body)).catch(
|
||||||
(err) => {
|
(err) => {
|
||||||
console.log(err.details);
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
err.details,
|
err.details,
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||||
@@ -73,27 +71,43 @@ export class ConnectorClientService implements OnModuleInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadConnectorsFile(uploadFile) {
|
async uploadConnectorsFile(uploadFile: Express.Multer.File) {
|
||||||
const connectors = JSON.parse(uploadFile.file.buffer.toString('utf8'));
|
const res = await lastValueFrom(
|
||||||
|
this.connectorServiceWrite.RegisterMultipleConnectorsWithoutImage({
|
||||||
const body = connectors.map((connector) => {
|
connectors: uploadFile.buffer,
|
||||||
return JSON.stringify(connector);
|
}),
|
||||||
});
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
connectors: body,
|
|
||||||
};
|
|
||||||
|
|
||||||
return lastValueFrom(
|
|
||||||
this.connectorServiceWrite.RegisterMultipleConnectorsWithoutImage(
|
|
||||||
request,
|
|
||||||
),
|
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
err.details,
|
err.details,
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
const responseParsed = JSON.parse(res.message);
|
||||||
|
const connectorsResponse = responseParsed.items.filter(
|
||||||
|
(i) => i.update._index === process.env.CONNECTORS_INDEX,
|
||||||
|
);
|
||||||
|
const connectorErrors = connectorsResponse
|
||||||
|
.filter((i) => i.update.error)
|
||||||
|
.map((i) => ({
|
||||||
|
id: i.update?._id,
|
||||||
|
reason: i.update?.error,
|
||||||
|
status: i.error?.status,
|
||||||
|
}));
|
||||||
|
const connectorCreated = connectorsResponse
|
||||||
|
.filter((i) => i.update.result === 'created')
|
||||||
|
.map((i) => i.update._id);
|
||||||
|
const connectorUpdated = connectorsResponse
|
||||||
|
.filter((i) => i.update.result === 'updated')
|
||||||
|
.map((i) => i.update._id);
|
||||||
|
const connectorNoop = connectorsResponse
|
||||||
|
.filter((i) => i.update.result === 'noop')
|
||||||
|
.map((i) => i.update._id);
|
||||||
|
return {
|
||||||
|
errors: connectorErrors,
|
||||||
|
created: connectorCreated,
|
||||||
|
updated: connectorUpdated,
|
||||||
|
noop: connectorNoop,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllConnectors(body) {
|
async getAllConnectors(body) {
|
||||||
@@ -118,13 +132,13 @@ export class ConnectorClientService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConnectorDetails(plugin: string, language: string) {
|
async getConnectorDetails(connectorId: string, language: string) {
|
||||||
const meta = new Metadata();
|
const meta = new Metadata();
|
||||||
meta.add('Language', language);
|
meta.add('Language', language);
|
||||||
return lastValueFrom(
|
return lastValueFrom(
|
||||||
this.connectorServiceRead.GetConnectorDetails(
|
this.connectorServiceRead.GetConnectorDetails(
|
||||||
{
|
{
|
||||||
plugin,
|
plugin: connectorId,
|
||||||
},
|
},
|
||||||
meta,
|
meta,
|
||||||
),
|
),
|
||||||
@@ -171,12 +185,7 @@ export class ConnectorClientService implements OnModuleInit {
|
|||||||
version,
|
version,
|
||||||
plugin,
|
plugin,
|
||||||
}),
|
}),
|
||||||
).catch((err) => {
|
|
||||||
throw new HttpException(
|
|
||||||
err.details,
|
|
||||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateConnector({ plugin, changes }) {
|
async updateConnector({ plugin, changes }) {
|
||||||
|
|||||||
@@ -11,16 +11,13 @@ import {
|
|||||||
UploadedFile,
|
UploadedFile,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
Inject,
|
Inject,
|
||||||
Headers,
|
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
||||||
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { ConnectorClientService } from './client.service';
|
import { ConnectorClientService } from './client.service';
|
||||||
import { AddTagDto } from './dtos/add-tag';
|
import { AddTagDto } from './dtos/add-tag';
|
||||||
import { CreateConnectorDto } from './dtos/create-connector';
|
import { CreateConnectorDto } from './dtos/create-connector';
|
||||||
import { UploadConnectorsFileDto } from './dtos/create-multiple-connector';
|
|
||||||
import { DeleteConnectorDto } from './dtos/delete-connector';
|
|
||||||
import { GetAllDto } from './dtos/get-all';
|
import { GetAllDto } from './dtos/get-all';
|
||||||
import { RemoveTagDto } from './dtos/remove-tag';
|
import { RemoveTagDto } from './dtos/remove-tag';
|
||||||
import { UpdateDto } from './dtos/update';
|
import { UpdateDto } from './dtos/update';
|
||||||
@@ -29,11 +26,14 @@ import {
|
|||||||
Authenticated,
|
Authenticated,
|
||||||
RequireAllPermissions,
|
RequireAllPermissions,
|
||||||
RequireSomePermission,
|
RequireSomePermission,
|
||||||
} from 'src/authentication/authentication.decorator';
|
} from 'src/decorators/authentication.decorator';
|
||||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
|
@ApiInternalOnlyController()
|
||||||
@ApiTags('connectors')
|
@ApiTags('connectors')
|
||||||
@ApiBearerAuth()
|
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
@Controller('connectors')
|
@Controller('connectors')
|
||||||
export class ConnectorController {
|
export class ConnectorController {
|
||||||
@@ -85,14 +85,10 @@ export class ConnectorController {
|
|||||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
|
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
|
||||||
@ApiConsumes('multipart/form-data')
|
@ApiConsumes('multipart/form-data')
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
async uploadConnectorsFile(@UploadedFile() file: UploadConnectorsFileDto) {
|
async uploadConnectorsFile(@UploadedFile() file: Express.Multer.File) {
|
||||||
console.log(`/upload`, 'Upload Connector Route');
|
this.logger.info(`/upload`, 'Upload Connector Route');
|
||||||
|
|
||||||
const response = await this.connectorClientService.uploadConnectorsFile({
|
return await this.connectorClientService.uploadConnectorsFile(file);
|
||||||
file,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@@ -104,11 +100,10 @@ export class ConnectorController {
|
|||||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||||
)
|
)
|
||||||
async getAllConnectors(
|
async getAllConnectors(
|
||||||
@Headers('dadosfera-lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Query() queries: GetAllDto,
|
@Query() queries: GetAllDto,
|
||||||
) {
|
) {
|
||||||
this.logger.info('/GET - getAllConnectors Route');
|
this.logger.info('/GET - getAllConnectors Route');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { search, size, page, ...filters } = queries;
|
const { search, size, page, ...filters } = queries;
|
||||||
|
|
||||||
@@ -149,12 +144,11 @@ export class ConnectorController {
|
|||||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||||
)
|
)
|
||||||
async getConnector(
|
async getConnector(
|
||||||
@Headers('dadosfera-lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Param('plugin') plugin: string,
|
@Param('plugin') plugin: string,
|
||||||
@Query('version') version: string,
|
@Query('version') version: string,
|
||||||
) {
|
) {
|
||||||
this.logger.info('/GET/:plugin - getConnector Route');
|
this.logger.info('/GET/:plugin - getConnector Route');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const pluginId = `${plugin}-${version}`;
|
const pluginId = `${plugin}-${version}`;
|
||||||
|
|
||||||
@@ -178,13 +172,11 @@ export class ConnectorController {
|
|||||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||||
)
|
)
|
||||||
async getConnectorDetails(
|
async getConnectorDetails(
|
||||||
@Headers('dadosfera-lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Param('plugin') plugin: string,
|
@Param('plugin') plugin: string,
|
||||||
@Query('version') version: string,
|
@Query('version') version: string,
|
||||||
) {
|
) {
|
||||||
this.logger.info('/upload - Upload Connector Route');
|
this.logger.info('/upload - Upload Connector Route');
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const pluginId = `${plugin}-${version}`;
|
const pluginId = `${plugin}-${version}`;
|
||||||
|
|
||||||
const response: any = await this.connectorClientService.getConnectorDetails(
|
const response: any = await this.connectorClientService.getConnectorDetails(
|
||||||
@@ -279,18 +271,15 @@ export class ConnectorController {
|
|||||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.DELETE)
|
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.DELETE)
|
||||||
async deleteConnector(
|
async deleteConnector(
|
||||||
@Param('plugin') plugin: string,
|
@Param('plugin') plugin: string,
|
||||||
@Body() { version }: DeleteConnectorDto,
|
@Query('version') version: string,
|
||||||
) {
|
) {
|
||||||
this.logger.info('/upload - Upload Connector Route');
|
this.logger.info('DELETE /:plugin - Delete Connector Route');
|
||||||
|
|
||||||
const response: any = await this.connectorClientService.deleteConnector(
|
const response = await this.connectorClientService.deleteConnector(
|
||||||
plugin,
|
plugin,
|
||||||
version,
|
version,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return { message: response.message, connector: response.connector };
|
||||||
message: response.message || 'ok',
|
|
||||||
connector: { ...JSON.parse(response.connector) },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Inject,
|
||||||
|
Param,
|
||||||
|
Put,
|
||||||
|
Query,
|
||||||
|
UseFilters,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiOkResponse, ApiProduces, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
|
import {
|
||||||
|
Authenticated,
|
||||||
|
RequireAllPermissions,
|
||||||
|
} from 'src/decorators/authentication.decorator';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { CustomersService } from './customers.service';
|
||||||
|
import { CustomerLinkRequest, CustomerLinksResponse } from './dtos/customers';
|
||||||
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
|
import type { StringValue } from 'ms';
|
||||||
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
|
||||||
|
@ApiTags('Customers')
|
||||||
|
@Controller('customers')
|
||||||
|
@UseFilters(GrpcToHttpExceptionFilter)
|
||||||
|
export class CustomersController {
|
||||||
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
dadosferaLogger: DadosferaLogger,
|
||||||
|
private customersService: CustomersService,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/links')
|
||||||
|
@Authenticated()
|
||||||
|
@ApiOkResponse({ type: CustomerLinksResponse })
|
||||||
|
async getCustomerLinks(@Param('id') id: string) {
|
||||||
|
this.logger.info('getCustomerLinks', { id });
|
||||||
|
const links = await this.customersService.getLinks(id);
|
||||||
|
return { links };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':id/links')
|
||||||
|
@Authenticated()
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||||
|
@ApiOkResponse()
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
async setCustomerLinks(
|
||||||
|
@Body() body: CustomerLinkRequest,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
const { links } = body;
|
||||||
|
this.logger.info('setCustomerLinks', { id, links });
|
||||||
|
await this.customersService.setLinks(id, links);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('token')
|
||||||
|
@Authenticated()
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.AUTH.permissions.GENERATE_TOKEN)
|
||||||
|
@ApiProduces('text/plain')
|
||||||
|
async getCustomerToken(
|
||||||
|
@Query('exp') exp: StringValue,
|
||||||
|
@User() user: RequestUser,
|
||||||
|
): Promise<string> {
|
||||||
|
this.logger.info('getCustomerToken', { exp, user });
|
||||||
|
const data = {
|
||||||
|
customerId: user.customer_id,
|
||||||
|
userId: user.user_id,
|
||||||
|
customerName: user.customer_name,
|
||||||
|
};
|
||||||
|
return this.customersService.generateToken(exp, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('monitoring-dashboard')
|
||||||
|
@Authenticated()
|
||||||
|
@RequireAllPermissions(
|
||||||
|
PERMISSIONS_GROUPS.CUSTOMER.permissions.MONITORING_DASHBOARD,
|
||||||
|
)
|
||||||
|
async getCustomerMonitoringDashboard(
|
||||||
|
@User() user: RequestUser,
|
||||||
|
): Promise<{ url: string }> {
|
||||||
|
this.logger.info('getCustomerMonitoringDashboard');
|
||||||
|
|
||||||
|
const metadata = PackTheMetadata(user);
|
||||||
|
return this.customersService.getMonitoringDashboardUrl(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
|
import { ClientsModule } from '@nestjs/microservices';
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { CustomersController } from './customers.controller';
|
||||||
|
import { CustomersService } from './customers.service';
|
||||||
|
import { PipelinesClientConfiguration } from '../pipelinesV2/pipelines-client';
|
||||||
|
|
||||||
|
const ducClient = new DucClient();
|
||||||
|
const piFactoryClient = new PipelinesClientConfiguration();
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ClientsModule.register([
|
||||||
|
ducClient.providerOptions,
|
||||||
|
piFactoryClient.providerOptions,
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
controllers: [CustomersController],
|
||||||
|
providers: [CustomersService, DadosferaLogger],
|
||||||
|
exports: [CustomersService],
|
||||||
|
})
|
||||||
|
export class CustomersModule {}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
OnModuleInit,
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
InternalServerErrorException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
|
import { firstValueFrom, lastValueFrom } from 'rxjs';
|
||||||
|
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
|
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||||
|
import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||||
|
import { CustomersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||||
|
import ErrorCodes from 'src/utils/errorCodes';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import {
|
||||||
|
GetSecretValueCommand,
|
||||||
|
SecretsManagerClient,
|
||||||
|
} from '@aws-sdk/client-secrets-manager';
|
||||||
|
import getEnv from 'src/utils/getEnv';
|
||||||
|
import { logger } from 'elastic-apm-node';
|
||||||
|
import {
|
||||||
|
ReadService,
|
||||||
|
ProtoServices as PipelineProtoServices,
|
||||||
|
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
import { PipelinesClientConfiguration } from '../pipelinesV2/pipelines-client';
|
||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||||
|
|
||||||
|
// This function will accept any string, which may result in a bug.
|
||||||
|
@Injectable()
|
||||||
|
export class CustomersService implements OnModuleInit {
|
||||||
|
private customerService: CustomersProtoService;
|
||||||
|
private logger: DadosferaLogger;
|
||||||
|
private pipelineReadService: ReadService.PipelineV2ReadService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
|
||||||
|
@Inject(PipelinesClientConfiguration.name)
|
||||||
|
private readonly pipelinesGrpcClient: ClientGrpc,
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
dadosferaLogger: DadosferaLogger,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
this.customerService = this.grpcClient.getService<CustomersProtoService>(
|
||||||
|
ProtoServices.CustomersProtoService,
|
||||||
|
);
|
||||||
|
this.pipelineReadService =
|
||||||
|
this.pipelinesGrpcClient.getService<ReadService.PipelineV2ReadService>(
|
||||||
|
PipelineProtoServices.PipelineV2ReadService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLinks(customerId: string) {
|
||||||
|
try {
|
||||||
|
const result = await lastValueFrom(
|
||||||
|
this.customerService.CustomerFindOneById({ id: customerId }),
|
||||||
|
);
|
||||||
|
return result.customer?.links || [];
|
||||||
|
} catch (err) {
|
||||||
|
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND)
|
||||||
|
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setLinks(customerId: string, links: Link[]) {
|
||||||
|
if (!customerId || !links) {
|
||||||
|
throw new HttpException(null, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await firstValueFrom(
|
||||||
|
this.customerService.CustomerUpdate({
|
||||||
|
id: customerId,
|
||||||
|
links,
|
||||||
|
} as CustomerUpdateRequest),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND)
|
||||||
|
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
|
||||||
|
else throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateToken(
|
||||||
|
expiresIn = '30m',
|
||||||
|
data: { customerId: string; userId: string; customerName: string },
|
||||||
|
) {
|
||||||
|
const { customerId, userId: generatedById, customerName } = data;
|
||||||
|
|
||||||
|
const secretsManagerClient = new SecretsManagerClient({});
|
||||||
|
|
||||||
|
const getSecretComand = new GetSecretValueCommand({
|
||||||
|
SecretId: `${getEnv()}/${customerName}/jwt-signing-key`,
|
||||||
|
});
|
||||||
|
let private_key_pem;
|
||||||
|
await secretsManagerClient
|
||||||
|
.send(getSecretComand)
|
||||||
|
.then((res) => {
|
||||||
|
const secret = JSON.parse(res.SecretString);
|
||||||
|
|
||||||
|
private_key_pem = secret.private_key_pem;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err.stack);
|
||||||
|
throw new InternalServerErrorException(
|
||||||
|
'Error finding keys for customer',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
let jwt_token;
|
||||||
|
try {
|
||||||
|
jwt_token = jwt.sign(
|
||||||
|
{
|
||||||
|
customerId,
|
||||||
|
generatedById,
|
||||||
|
},
|
||||||
|
private_key_pem,
|
||||||
|
{
|
||||||
|
algorithm: 'RS256',
|
||||||
|
expiresIn,
|
||||||
|
issuer: 'maestro',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err.stack);
|
||||||
|
throw new InternalServerErrorException('Error generating token');
|
||||||
|
}
|
||||||
|
// const verify = jwt.verify(jwt_token, public_key_pem, {
|
||||||
|
// algorithms: ['RS256'],
|
||||||
|
// issuer: 'maestro',
|
||||||
|
// });
|
||||||
|
// const decoded = jwt.decode(jwt_token, { complete: true });
|
||||||
|
return jwt_token;
|
||||||
|
}
|
||||||
|
async getMonitoringDashboardUrl(metadata: Metadata) {
|
||||||
|
logger.info('CustomersService - getMonitoringDashboardUrl');
|
||||||
|
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.pipelineReadService.PipelineV2GetDashboardUrl(
|
||||||
|
{
|
||||||
|
dashboard_id: '45',
|
||||||
|
exp: '15m',
|
||||||
|
metabase_customer_name: 'dadosferatech',
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
logger.info('Done');
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CustomerLink implements Link {
|
||||||
|
@ApiProperty()
|
||||||
|
href: string;
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
iconSrc: string;
|
||||||
|
}
|
||||||
|
export class CustomerLinkRequest {
|
||||||
|
@ApiProperty({ type: [CustomerLink] })
|
||||||
|
links: CustomerLink[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomerLinksResponse {
|
||||||
|
@ApiProperty({ type: [CustomerLink] })
|
||||||
|
links: CustomerLink[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,10 @@ import {
|
|||||||
ProtoPaths,
|
ProtoPaths,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/Duc';
|
} from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.DUC_URL.startsWith('duc:') ||
|
||||||
|
process.env.DUC_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class DucClient {
|
export class DucClient {
|
||||||
public name = 'DucClient';
|
public name = 'DucClient';
|
||||||
|
|
||||||
@@ -16,18 +20,17 @@ export class DucClient {
|
|||||||
transport: Transport.GRPC,
|
transport: Transport.GRPC,
|
||||||
options: {
|
options: {
|
||||||
url: process.env.DUC_URL,
|
url: process.env.DUC_URL,
|
||||||
package: ProtoPackages.WritePackage,
|
package: [ProtoPackages.WritePackage, ProtoPackages.ReadPackage],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
protoPath: [ProtoPaths.WriteFilePath, ProtoPaths.ReadFilePath],
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: ProtoPaths.WriteFilePath,
|
|
||||||
loader: {
|
loader: {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
enums: String,
|
enums: String,
|
||||||
objects: true,
|
objects: true,
|
||||||
arrays: true,
|
arrays: true,
|
||||||
},
|
},
|
||||||
|
maxSendMessageLength: 15 * 1024 * 1024, // 15 MB por mensagem
|
||||||
|
maxReceiveMessageLength: 15 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
||||||
import { Info } from 'protospack/dist/lib/interfaces';
|
|
||||||
|
|
||||||
export class GoogleAnalyticsClientSecrets {
|
|
||||||
@ApiProperty()
|
|
||||||
type: string;
|
|
||||||
@ApiProperty()
|
|
||||||
project_id: string;
|
|
||||||
@ApiProperty()
|
|
||||||
private_key_id: string;
|
|
||||||
@ApiProperty()
|
|
||||||
private_key: string;
|
|
||||||
@ApiProperty()
|
|
||||||
client_email: string;
|
|
||||||
@ApiProperty()
|
|
||||||
client_id: string;
|
|
||||||
@ApiProperty()
|
|
||||||
auth_uri: string;
|
|
||||||
@ApiProperty()
|
|
||||||
token_uri: string;
|
|
||||||
@ApiProperty()
|
|
||||||
auth_provider_x509_cert_url: string;
|
|
||||||
@ApiProperty()
|
|
||||||
client_x509_cert_url: string;
|
|
||||||
}
|
|
||||||
export class OauthObject {
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
get_tokens_url: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
get_tokens_url_params: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
get_tokens_set_response: Record<string, any>;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
content_type: string;
|
|
||||||
}
|
|
||||||
export class InputOptions {
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
oauth: OauthObject;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
skip_select_columns: true;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
skip_select_entities: false;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
skip_transformation: true;
|
|
||||||
}
|
|
||||||
export class FileFormatParams {
|
|
||||||
@ApiProperty()
|
|
||||||
sep: string;
|
|
||||||
@ApiProperty()
|
|
||||||
header: boolean;
|
|
||||||
@ApiProperty()
|
|
||||||
encoding: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthParameters {
|
|
||||||
@ApiProperty()
|
|
||||||
aws_access_key_id: string;
|
|
||||||
@ApiProperty()
|
|
||||||
aws_secret_access_key: string;
|
|
||||||
}
|
|
||||||
export class CredentialsJdbc {
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
jdbc_user: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
jdbc_password: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
database: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
endpoint: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
port: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
engine: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
schema: string;
|
|
||||||
}
|
|
||||||
export class Credentials extends CredentialsJdbc {
|
|
||||||
@ApiProperty()
|
|
||||||
connection_type: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
client_aws_access_key_id: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
client_aws_secret_access_key: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
client_bucket: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
file_to_extract: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
file_format_params: FileFormatParams;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
view_id: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
client_secrets: GoogleAnalyticsClientSecrets;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
start_date: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
end_date: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
oauth_code: string;
|
|
||||||
}
|
|
||||||
export class Column {
|
export class Column {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
name: string;
|
name: string;
|
||||||
@@ -114,6 +13,14 @@ export class TableColumns {
|
|||||||
columns: string[];
|
columns: string[];
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
references: Column[];
|
references: Column[];
|
||||||
|
@ApiProperty()
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
export class AvailableEntity {
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
@ApiProperty()
|
||||||
|
replication_methods: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Input {
|
export class Input {
|
||||||
@@ -133,26 +40,6 @@ export class Input {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
|
||||||
//deprecated attributes (for retro compatibility)
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
category: string;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
credentials: Credentials;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
options: InputOptions;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
info: Info;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
cron: string;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
source_bucket: string;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
source_prefix: string;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
auth_parameters: AuthParameters;
|
|
||||||
@ApiPropertyOptional({ deprecated: true })
|
|
||||||
file_format_params: FileFormatParams;
|
|
||||||
}
|
}
|
||||||
export class GetAvailableEntitiesReq {
|
export class GetAvailableEntitiesReq {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -160,43 +47,10 @@ export class GetAvailableEntitiesReq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class GetAvailableEntitiesRes {
|
export class GetAvailableEntitiesRes {
|
||||||
@ApiProperty()
|
@ApiProperty({ type: [AvailableEntity] })
|
||||||
entities: string[];
|
entities: AvailableEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestConnectionGetColumnsReq {
|
|
||||||
@ApiProperty()
|
|
||||||
plugin: string;
|
|
||||||
@ApiProperty()
|
|
||||||
tables: string[];
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
credentials: CredentialsJdbc;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
id: string;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
info: Info;
|
|
||||||
}
|
|
||||||
export class TestConnectionGetColumnsRes {
|
|
||||||
@ApiProperty({ type: [TableColumns] })
|
|
||||||
tables: TableColumns[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TestConnectionReq {
|
|
||||||
@ApiProperty()
|
|
||||||
plugin: string;
|
|
||||||
@ApiProperty()
|
|
||||||
credentials: CredentialsJdbc;
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
info: Info;
|
|
||||||
}
|
|
||||||
export class TestConnectionRes {
|
|
||||||
@ApiProperty()
|
|
||||||
connection_state: boolean;
|
|
||||||
@ApiProperty()
|
|
||||||
total_entities: number;
|
|
||||||
@ApiProperty()
|
|
||||||
database_tables: string[];
|
|
||||||
}
|
|
||||||
export class CreateInputReq extends OmitType(Input, [
|
export class CreateInputReq extends OmitType(Input, [
|
||||||
'id',
|
'id',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Info } from 'protospack/dist/lib/interfaces';
|
import { Info } from '@dadosfera/protospack/dist/lib/interfaces';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
jdbc_user: string;
|
jdbc_user: string;
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { Input } from '@dadosfera/protospack-v2';
|
|||||||
import { credentials } from '@grpc/grpc-js';
|
import { credentials } from '@grpc/grpc-js';
|
||||||
import {
|
import {
|
||||||
ClientProviderOptions,
|
ClientProviderOptions,
|
||||||
GrpcOptions,
|
|
||||||
Transport,
|
Transport,
|
||||||
|
type GrpcOptions,
|
||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||||
|
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||||
export class InputsGrpcClient {
|
export class InputsGrpcClient {
|
||||||
public readonly name = 'InputsGrpcClient';
|
public readonly name = 'InputsGrpcClient';
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
@@ -16,10 +19,7 @@ export class InputsGrpcClient {
|
|||||||
Input.ProtoPackages.WritePackage,
|
Input.ProtoPackages.WritePackage,
|
||||||
Input.ProtoPackages.ReadPackage,
|
Input.ProtoPackages.ReadPackage,
|
||||||
],
|
],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: [
|
protoPath: [
|
||||||
Input.ProtoPaths.WriteFilePath,
|
Input.ProtoPaths.WriteFilePath,
|
||||||
Input.ProtoPaths.ReadFilePath,
|
Input.ProtoPaths.ReadFilePath,
|
||||||
|
|||||||
@@ -7,27 +7,26 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
|
UseFilters,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InputsService } from './inputs.service';
|
import { InputsService } from './inputs.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
||||||
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
|
import { AuthenticateCondition } from 'src/decorators/authentication.decorator';
|
||||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import {
|
import {
|
||||||
CreateInputReq,
|
CreateInputReq,
|
||||||
GetAvailableEntitiesReq,
|
GetAvailableEntitiesReq,
|
||||||
GetAvailableEntitiesRes,
|
GetAvailableEntitiesRes,
|
||||||
Input,
|
Input,
|
||||||
TestConnectionGetColumnsReq,
|
|
||||||
TestConnectionGetColumnsRes,
|
|
||||||
TestConnectionReq,
|
|
||||||
TestConnectionRes,
|
|
||||||
} from './dtos/input.model';
|
} from './dtos/input.model';
|
||||||
import { UpdateInputRequest } from './dtos/old_interfaces';
|
import { UpdateInputRequest } from './dtos/old_interfaces';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
|
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
@ApiTags('inputs')
|
@ApiTags('Inputs')
|
||||||
@Controller('inputs')
|
@Controller('inputs')
|
||||||
@AuthenticateCondition((req, user) => {
|
@AuthenticateCondition((req, user) => {
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
@@ -38,7 +37,9 @@ import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entitie
|
|||||||
user.permissions.includes(
|
user.permissions.includes(
|
||||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
|
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
|
||||||
) ||
|
) ||
|
||||||
user.permissions.includes(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid)
|
user.permissions.includes(
|
||||||
|
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -49,10 +50,13 @@ import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entitie
|
|||||||
user.permissions.includes(
|
user.permissions.includes(
|
||||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
|
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
|
||||||
) ||
|
) ||
|
||||||
user.permissions.includes(PERMISSIONS_GROUPS.PIPELINE.permissions.GET.seqid)
|
user.permissions.includes(
|
||||||
|
PERMISSIONS_GROUPS.PIPELINE.permissions.GET.seqid,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
export class InputsController {
|
export class InputsController {
|
||||||
logger: DadosferaLogger;
|
logger: DadosferaLogger;
|
||||||
constructor(
|
constructor(
|
||||||
@@ -63,44 +67,23 @@ export class InputsController {
|
|||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('available-entities/:plugin')
|
@Get('available-entities/:plugin')
|
||||||
@ApiOkResponse({ type: GetAvailableEntitiesRes })
|
@ApiOkResponse({ type: GetAvailableEntitiesRes })
|
||||||
async getAvailableEntities(
|
async getAvailableEntities(
|
||||||
@Body() body,
|
|
||||||
@Param() params: GetAvailableEntitiesReq,
|
@Param() params: GetAvailableEntitiesReq,
|
||||||
|
@User() user: RequestUser,
|
||||||
) {
|
) {
|
||||||
const { info } = body;
|
|
||||||
const { plugin } = params;
|
const { plugin } = params;
|
||||||
return await this.inputService.getAvailableEntities({ info, plugin });
|
const availableEntities = await this.inputService.getAvailableEntities({
|
||||||
}
|
info: {
|
||||||
|
customer: user.customer_name,
|
||||||
@Post('/test-connection')
|
customer_id: user.customer_id,
|
||||||
@ApiOkResponse({ type: TestConnectionRes })
|
user_id: user.user_id,
|
||||||
async testConnection(@Body() data: TestConnectionReq) {
|
|
||||||
this.logger.info(`/test-connection - ON TEST CONNECTION ROUTE`, {
|
|
||||||
user: data.info.user_id,
|
|
||||||
customer: data.info.customer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.inputService.testConnection(data);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/test-connection/get-columns')
|
|
||||||
@ApiOkResponse({ type: TestConnectionGetColumnsRes })
|
|
||||||
async getColumns(@Body() data: TestConnectionGetColumnsReq) {
|
|
||||||
this.logger.info(
|
|
||||||
`/test-connection/get-columns - ON TEST CONNECTION GET COLUMNS ROUTE`,
|
|
||||||
{
|
|
||||||
user: data.info.user_id,
|
|
||||||
customer: data.info.customer,
|
|
||||||
},
|
},
|
||||||
);
|
plugin,
|
||||||
|
});
|
||||||
const response = await this.inputService.getColumns(data);
|
return availableEntities;
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@@ -121,6 +104,7 @@ export class InputsController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get()
|
@Get()
|
||||||
async findAll(@User() user: RequestUser) {
|
async findAll(@User() user: RequestUser) {
|
||||||
const { user_id, customer_id, customer_name: customer } = user;
|
const { user_id, customer_id, customer_name: customer } = user;
|
||||||
@@ -139,8 +123,7 @@ export class InputsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:id')
|
@Get('/:id')
|
||||||
async findOne(@Body() body, @Param() params) {
|
async findOne(@Body() body, @Param('id') id: string) {
|
||||||
const { id } = params;
|
|
||||||
this.logger.info(`/input/${id} - ON FIND ONE ROUTE`, {
|
this.logger.info(`/input/${id} - ON FIND ONE ROUTE`, {
|
||||||
user: body.info.user_id,
|
user: body.info.user_id,
|
||||||
customer: body.info.customer,
|
customer: body.info.customer,
|
||||||
@@ -151,6 +134,7 @@ export class InputsController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
async update(@Body() updateInputDto: UpdateInputRequest, @Param() params) {
|
async update(@Body() updateInputDto: UpdateInputRequest, @Param() params) {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
@@ -166,6 +150,7 @@ export class InputsController {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async delete(@Body() data, @Param() params) {
|
async delete(@Body() data, @Param() params) {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Timeout } from '@nestjs/schedule';
|
|
||||||
import CronParser, { CronExpression } from 'cron-parser';
|
import CronParser, { CronExpression } from 'cron-parser';
|
||||||
import { ClientGrpc } from '@nestjs/microservices';
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
||||||
@@ -14,10 +13,10 @@ import { objectCamelToSnake } from 'src/utils/CaseConverter';
|
|||||||
import { IIdRequest, UpdateInputRequest } from './dtos/old_interfaces';
|
import { IIdRequest, UpdateInputRequest } from './dtos/old_interfaces';
|
||||||
import { Input } from '@dadosfera/protospack-v2';
|
import { Input } from '@dadosfera/protospack-v2';
|
||||||
import {
|
import {
|
||||||
|
GetAvailableEntitiesRequest,
|
||||||
InputCreateGenericRequest,
|
InputCreateGenericRequest,
|
||||||
InputCreateS3Request,
|
InputCreateS3Request,
|
||||||
InputNewCreateRequest,
|
InputNewCreateRequest,
|
||||||
TestConnectionGetColumnsRequest,
|
|
||||||
TestConnectionRequest,
|
TestConnectionRequest,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/messages';
|
} from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/messages';
|
||||||
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
|
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
|
||||||
@@ -72,17 +71,6 @@ export class InputsService {
|
|||||||
objectCamelToSnake(createInputResponse);
|
objectCamelToSnake(createInputResponse);
|
||||||
return createInputResponse;
|
return createInputResponse;
|
||||||
},
|
},
|
||||||
findOne: async (data: IIdRequest) => {
|
|
||||||
this.logger.info('InputClientService - FindOne');
|
|
||||||
const findOneInputResponse = await lastValueFrom(
|
|
||||||
this.inputReadService.InputFindOne(data),
|
|
||||||
).catch((e) => {
|
|
||||||
this.logger.error(e.details);
|
|
||||||
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
});
|
|
||||||
objectCamelToSnake(findOneInputResponse);
|
|
||||||
return findOneInputResponse;
|
|
||||||
},
|
|
||||||
update: async (updateInputDTO: UpdateInputRequest) => {
|
update: async (updateInputDTO: UpdateInputRequest) => {
|
||||||
this.logger.info('InputClientService - Update');
|
this.logger.info('InputClientService - Update');
|
||||||
const updateInputResponse = await lastValueFrom(
|
const updateInputResponse = await lastValueFrom(
|
||||||
@@ -91,14 +79,6 @@ export class InputsService {
|
|||||||
|
|
||||||
return updateInputResponse;
|
return updateInputResponse;
|
||||||
},
|
},
|
||||||
remove: async (idRequest: IIdRequest) => {
|
|
||||||
this.logger.info('InputClientService - Remove');
|
|
||||||
const removeInputResponse = await lastValueFrom(
|
|
||||||
this.inputWriteService.InputRemove(idRequest),
|
|
||||||
);
|
|
||||||
|
|
||||||
return removeInputResponse;
|
|
||||||
},
|
|
||||||
testConnection: async (data: TestConnectionRequest) => {
|
testConnection: async (data: TestConnectionRequest) => {
|
||||||
this.logger.info('InputClientService - TestConnection');
|
this.logger.info('InputClientService - TestConnection');
|
||||||
const testConnectionResponse = await lastValueFrom(
|
const testConnectionResponse = await lastValueFrom(
|
||||||
@@ -107,32 +87,6 @@ export class InputsService {
|
|||||||
|
|
||||||
return testConnectionResponse;
|
return testConnectionResponse;
|
||||||
},
|
},
|
||||||
getColumns: async (data: TestConnectionGetColumnsRequest) => {
|
|
||||||
this.logger.info('InputClientService - TestConnection/Get-Columns');
|
|
||||||
const getColumnsResponse = await new Promise((resolve, reject) => {
|
|
||||||
this.inputReadService.GetColumns(data).subscribe({
|
|
||||||
next(x) {
|
|
||||||
resolve(objectCamelToSnake(x));
|
|
||||||
},
|
|
||||||
error(err) {
|
|
||||||
reject(err);
|
|
||||||
},
|
|
||||||
complete() {
|
|
||||||
// console.log('done');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
this.logger.info('Done');
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return getColumnsResponse;
|
|
||||||
},
|
|
||||||
getAvailableEntities: async (data) => {
|
getAvailableEntities: async (data) => {
|
||||||
this.logger.info('InputClientService - GetAvailableEntities');
|
this.logger.info('InputClientService - GetAvailableEntities');
|
||||||
data;
|
data;
|
||||||
@@ -207,12 +161,9 @@ export class InputsService {
|
|||||||
// this.validateCron(data);
|
// this.validateCron(data);
|
||||||
const { body, info } = data;
|
const { body, info } = data;
|
||||||
|
|
||||||
if (body.info) delete body.info;
|
const inputCreateGenericRequest: InputCreateGenericRequest = {
|
||||||
|
|
||||||
const inputCreateGenericRequest: any | InputCreateGenericRequest = {
|
|
||||||
input: {
|
input: {
|
||||||
...body,
|
...body,
|
||||||
credentials: body.credentials && JSON.stringify(body.credentials),
|
|
||||||
},
|
},
|
||||||
info,
|
info,
|
||||||
};
|
};
|
||||||
@@ -224,8 +175,8 @@ export class InputsService {
|
|||||||
return { input: adjustedInput };
|
return { input: adjustedInput };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAvailableEntities(data): Promise<{ entities: string[] }> {
|
async getAvailableEntities(data: GetAvailableEntitiesRequest) {
|
||||||
return await this.OLD_inputClient.getAvailableEntities(data);
|
return lastValueFrom(this.inputReadService.GetAvailableEntities(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(info: Info) {
|
async findAll(info: Info) {
|
||||||
@@ -239,17 +190,13 @@ export class InputsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findOne(idRequest: IIdRequest) {
|
async findOne(idRequest: IIdRequest) {
|
||||||
try {
|
const findOneInputResponse: any = await lastValueFrom(
|
||||||
const findOneInputResponse: any = await this.OLD_inputClient.findOne(
|
this.inputReadService.InputFindOne(idRequest),
|
||||||
idRequest,
|
|
||||||
);
|
);
|
||||||
findOneInputResponse.input = this.adjustInputPayload(
|
findOneInputResponse.input = this.adjustInputPayload(
|
||||||
findOneInputResponse.input,
|
findOneInputResponse.input,
|
||||||
);
|
);
|
||||||
return findOneInputResponse;
|
return findOneInputResponse;
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data, info: Info) {
|
async update(id: string, data, info: Info) {
|
||||||
@@ -271,16 +218,9 @@ export class InputsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(idRequest: IIdRequest) {
|
async remove(idRequest: IIdRequest) {
|
||||||
try {
|
return lastValueFrom(this.inputWriteService.InputRemove(idRequest));
|
||||||
const removeInputResponse = await this.OLD_inputClient.remove(idRequest);
|
|
||||||
|
|
||||||
return removeInputResponse;
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timeout(60000 * 10) // Timeout set for 10 minutes
|
|
||||||
async testConnection(data) {
|
async testConnection(data) {
|
||||||
try {
|
try {
|
||||||
const testConnectionInputResponse =
|
const testConnectionInputResponse =
|
||||||
@@ -292,17 +232,6 @@ export class InputsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getColumns(data) {
|
|
||||||
try {
|
|
||||||
const testConnectionGetColumnsResponse =
|
|
||||||
await this.OLD_inputClient.getColumns(data);
|
|
||||||
|
|
||||||
return testConnectionGetColumnsResponse;
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
generateInputS3Payload(payload) {
|
generateInputS3Payload(payload) {
|
||||||
const { credentials, plugin, cron } = payload;
|
const { credentials, plugin, cron } = payload;
|
||||||
if (!credentials) return payload;
|
if (!credentials) return payload;
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { Body, Controller, Inject, Param, Post } from '@nestjs/common';
|
||||||
|
import { init } from 'mixpanel';
|
||||||
|
import { Authenticated } from 'src/decorators/authentication.decorator';
|
||||||
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
|
|
||||||
|
@ApiInternalOnlyController()
|
||||||
|
@Authenticated()
|
||||||
|
@Controller('trackEvent')
|
||||||
|
export class MixpanelController {
|
||||||
|
constructor(
|
||||||
|
@Inject('MIXPANEL_TOKEN')
|
||||||
|
private readonly mixpanelToken: string,
|
||||||
|
) {}
|
||||||
|
@Post(':id')
|
||||||
|
async trackEvent(@Param('id') id, @Body() body, @User() user: RequestUser) {
|
||||||
|
delete body.info;
|
||||||
|
const mixpanel = init(this.mixpanelToken);
|
||||||
|
|
||||||
|
const separator = user.username.includes('-') ? '-' : '.';
|
||||||
|
const removeValues = [
|
||||||
|
'.dadosferatech.dadosfera',
|
||||||
|
'.demo.dadosfera',
|
||||||
|
'.dadosferademo',
|
||||||
|
'.dadosferarh.dadosfera',
|
||||||
|
'.dadosferatech.dadosfera2',
|
||||||
|
'.dadosferatech.dadosfera',
|
||||||
|
'.dadosfera.fin',
|
||||||
|
'.dadosferafin.dadosfera',
|
||||||
|
'.praxio.dadosfera',
|
||||||
|
'.dadosfera.tech',
|
||||||
|
'.treinamentos@dadosfera.ai',
|
||||||
|
'.dadosfera2',
|
||||||
|
'.treinamentosfera',
|
||||||
|
'.dadosfera',
|
||||||
|
];
|
||||||
|
|
||||||
|
let username = user.username;
|
||||||
|
|
||||||
|
removeValues.forEach((value) => {
|
||||||
|
username = username.replace(value, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
username = username.split('@')?.[0];
|
||||||
|
username = username.split('+')?.[0];
|
||||||
|
|
||||||
|
let firstName = username
|
||||||
|
.substring(0, username.indexOf(separator))
|
||||||
|
.replace('dadosfera', '');
|
||||||
|
let lastName = username
|
||||||
|
.substring(username.lastIndexOf(separator) + 1)
|
||||||
|
.replace('dadosfera', '');
|
||||||
|
|
||||||
|
if (!firstName) {
|
||||||
|
firstName = lastName;
|
||||||
|
lastName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
firstName = this.capitalize(firstName);
|
||||||
|
lastName = this.capitalize(lastName);
|
||||||
|
|
||||||
|
await mixpanel.people.set(user.username, {
|
||||||
|
$first_name: firstName,
|
||||||
|
$last_name: lastName,
|
||||||
|
$name: this.getFullName(firstName, lastName),
|
||||||
|
$email: user.username.includes('@')
|
||||||
|
? user.username
|
||||||
|
: user.username + '@dadosfera.ai',
|
||||||
|
customer_name: user.customer_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
await mixpanel.track(id, {
|
||||||
|
distinct_id: user.username,
|
||||||
|
customer: user.customer_name,
|
||||||
|
env: process.env.ENV,
|
||||||
|
...body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { id, body, user: user.username };
|
||||||
|
}
|
||||||
|
|
||||||
|
capitalize(sentence: string): string {
|
||||||
|
if (!sentence) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sentence[0].toUpperCase() + sentence.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullName(firstName: string, lastName: string) {
|
||||||
|
return `${firstName}${lastName ? ' ' + lastName : ''}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { getSecretFromSecretsManager } from 'src/utils/SecretManager';
|
||||||
|
import { MixpanelController } from './mixpanel.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [MixpanelController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: 'MIXPANEL_TOKEN',
|
||||||
|
useValue: getSecretFromSecretsManager(
|
||||||
|
`${process.env.ENV}/root/mixpanel_token`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class MixpanelModule {}
|
||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
import { NetworkConfig } from '@dadosfera/protospack-v2';
|
import { NetworkConfig } from '@dadosfera/protospack-v2';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||||
|
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class NetworkConfigGrpcClient {
|
export class NetworkConfigGrpcClient {
|
||||||
public name = 'NetworkConfigGrpcClient';
|
public name = 'NetworkConfigGrpcClient';
|
||||||
|
|
||||||
@@ -17,8 +21,7 @@ export class NetworkConfigGrpcClient {
|
|||||||
NetworkConfig.ProtoPackages.ReadPackage,
|
NetworkConfig.ProtoPackages.ReadPackage,
|
||||||
NetworkConfig.ProtoPackages.WritePackage,
|
NetworkConfig.ProtoPackages.WritePackage,
|
||||||
],
|
],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.ENV === 'local' ? undefined : credentials.createSsl(),
|
|
||||||
protoPath: [
|
protoPath: [
|
||||||
NetworkConfig.ProtoPaths.ReadFilePath,
|
NetworkConfig.ProtoPaths.ReadFilePath,
|
||||||
NetworkConfig.ProtoPaths.WriteFilePath,
|
NetworkConfig.ProtoPaths.WriteFilePath,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
|
||||||
Get,
|
Get,
|
||||||
Headers,
|
|
||||||
Inject,
|
Inject,
|
||||||
Param,
|
Param,
|
||||||
Patch,
|
|
||||||
Post,
|
Post,
|
||||||
Query,
|
Query,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
@@ -15,21 +12,24 @@ import {
|
|||||||
import { NetworkConfigService } from './network-config.service';
|
import { NetworkConfigService } from './network-config.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { ApiConsumes, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiConsumes, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
import {
|
import {
|
||||||
CreateNetworkConfigReq,
|
CreateNetworkConfigReq,
|
||||||
CreateNetworkConfigRes,
|
CreateNetworkConfigRes,
|
||||||
DeleteNetworkConfigRes,
|
|
||||||
FindAllNetworkConfigsRes,
|
FindAllNetworkConfigsRes,
|
||||||
UpdateNetworkConfigReq,
|
|
||||||
UpdateNetworkConfigRes,
|
|
||||||
} from './dto/network-config';
|
} from './dto/network-config';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
import { RequireAllPermissions } from 'src/authentication/authentication.decorator';
|
import {
|
||||||
|
Authenticated,
|
||||||
|
RequireAllPermissions,
|
||||||
|
} from 'src/decorators/authentication.decorator';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
|
||||||
const networkConfigPermissions = PERMISSIONS_GROUPS.NETWORK_CONFIG.permissions;
|
const networkConfigPermissions = PERMISSIONS_GROUPS.NETWORK_CONFIG.permissions;
|
||||||
@ApiTags('Network Config')
|
@ApiTags('Network Config')
|
||||||
|
@Authenticated()
|
||||||
@Controller('network-config')
|
@Controller('network-config')
|
||||||
export class NetworkConfigController {
|
export class NetworkConfigController {
|
||||||
logger: any;
|
logger: any;
|
||||||
@@ -39,18 +39,18 @@ export class NetworkConfigController {
|
|||||||
) {
|
) {
|
||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
|
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
|
||||||
async findAllNetworkConfigs(
|
async findAllNetworkConfigs(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Query() queries,
|
@Query() queries,
|
||||||
) {
|
) {
|
||||||
this.logger.info('GET /network-config', {
|
this.logger.info('GET /network-config', {
|
||||||
user: user.user_id,
|
user: user.user_id,
|
||||||
customer: user.customer_name,
|
customer: user.customer_name,
|
||||||
});
|
});
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { user_id, customer_id, customer_name } = user;
|
const { user_id, customer_id, customer_name } = user;
|
||||||
const { search, size, page, ...filters } = queries;
|
const { search, size, page, ...filters } = queries;
|
||||||
@@ -78,14 +78,14 @@ export class NetworkConfigController {
|
|||||||
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
|
@ApiOkResponse({ type: FindAllNetworkConfigsRes })
|
||||||
async findNetworkConfigByIdDetails(
|
async findNetworkConfigByIdDetails(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
) {
|
) {
|
||||||
this.logger.info('GET /network-config/:id', {
|
this.logger.info('GET /network-config/:id', {
|
||||||
user: user.user_id,
|
user: user.user_id,
|
||||||
customer: user.customer_name,
|
customer: user.customer_name,
|
||||||
});
|
});
|
||||||
if (!language) language = 'en-us';
|
|
||||||
return this.networkConfigService.findNetworkConfigByIdDetails(id, {
|
return this.networkConfigService.findNetworkConfigByIdDetails(id, {
|
||||||
customer_name: user.customer_name,
|
customer_name: user.customer_name,
|
||||||
customer_id: user.customer_id,
|
customer_id: user.customer_id,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ConnectionClientService } from '../connection/client.service';
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
||||||
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||||
@ApiTags('oauth')
|
@ApiTags('oauth')
|
||||||
@Controller('oauth')
|
@Controller('oauth')
|
||||||
export class OauthController {
|
export class OauthController {
|
||||||
@@ -15,15 +16,14 @@ export class OauthController {
|
|||||||
private logger: DadosferaLogger,
|
private logger: DadosferaLogger,
|
||||||
) {
|
) {
|
||||||
switch (process.env.ENV) {
|
switch (process.env.ENV) {
|
||||||
case 'dev':
|
|
||||||
case 'stg':
|
case 'stg':
|
||||||
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/coletix/auth-callback`;
|
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/collect/auth-callback`;
|
||||||
break;
|
break;
|
||||||
case 'prd':
|
case 'prd':
|
||||||
this.redirectUrl = `https://app.dadosfera.ai/coletix/auth-callback`;
|
this.redirectUrl = `https://app.dadosfera.ai/collect/auth-callback`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.redirectUrl = `http://localhost:4200/coletix/auth-callback`;
|
this.redirectUrl = `http://localhost:4200/collect/auth-callback`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Get('hubspot')
|
@Get('hubspot')
|
||||||
@@ -32,6 +32,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('hubspot/callback')
|
@Get('hubspot/callback')
|
||||||
@UseGuards(AuthGuard('hubspot'))
|
@UseGuards(AuthGuard('hubspot'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -45,6 +46,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('googleads/callback')
|
@Get('googleads/callback')
|
||||||
@UseGuards(AuthGuard('google'))
|
@UseGuards(AuthGuard('google'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -58,6 +60,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('google-sheets/callback')
|
@Get('google-sheets/callback')
|
||||||
@UseGuards(AuthGuard('google'))
|
@UseGuards(AuthGuard('google'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -71,6 +74,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('facebook/callback')
|
@Get('facebook/callback')
|
||||||
@UseGuards(AuthGuard('facebook'))
|
@UseGuards(AuthGuard('facebook'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -84,6 +88,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('mailchimp/callback')
|
@Get('mailchimp/callback')
|
||||||
@UseGuards(AuthGuard('mailchimp'))
|
@UseGuards(AuthGuard('mailchimp'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -97,6 +102,7 @@ export class OauthController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
@Get('salesforce/callback')
|
@Get('salesforce/callback')
|
||||||
@UseGuards(AuthGuard('salesforce'))
|
@UseGuards(AuthGuard('salesforce'))
|
||||||
@Redirect()
|
@Redirect()
|
||||||
@@ -148,7 +154,13 @@ export class OauthController {
|
|||||||
const { customer_id, customer_name, user_id } = connectionInfo;
|
const { customer_id, customer_name, user_id } = connectionInfo;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.connectionService.createConnection(
|
const response = connectionInfo.id
|
||||||
|
? await this.connectionService.updateConnection(
|
||||||
|
connectionInfo.id,
|
||||||
|
connectionInfo,
|
||||||
|
PackTheMetadata({ customer_id, customer_name, user_id }),
|
||||||
|
)
|
||||||
|
: await this.connectionService.createConnection(
|
||||||
connectionInfo,
|
connectionInfo,
|
||||||
PackTheMetadata({ customer_id, customer_name, user_id }),
|
PackTheMetadata({ customer_id, customer_name, user_id }),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class OauthService {
|
|||||||
user_id,
|
user_id,
|
||||||
customer_id,
|
customer_id,
|
||||||
customer_name,
|
customer_name,
|
||||||
|
id,
|
||||||
} = query;
|
} = query;
|
||||||
|
|
||||||
const properties = { plugin, credentials_type: 'oauth' };
|
const properties = { plugin, credentials_type: 'oauth' };
|
||||||
@@ -45,6 +46,7 @@ export class OauthService {
|
|||||||
type,
|
type,
|
||||||
properties,
|
properties,
|
||||||
plugin,
|
plugin,
|
||||||
|
id,
|
||||||
};
|
};
|
||||||
return jwt.sign({ ...newConnection }, process.env.JWT_PRIVATE_KEY);
|
return jwt.sign({ ...newConnection }, process.env.JWT_PRIVATE_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export class FacebookStrategy extends PassportStrategy(Strategy) {
|
|||||||
req,
|
req,
|
||||||
'application',
|
'application',
|
||||||
);
|
);
|
||||||
options.scope = 'ads_read ads_management';
|
options.scope =
|
||||||
|
'pages_show_list ads_read pages_read_engagement ads_management';
|
||||||
super.authenticate(req, options);
|
super.authenticate(req, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
|||||||
|
|
||||||
super(options, verify);
|
super(options, verify);
|
||||||
switch (process.env.ENV) {
|
switch (process.env.ENV) {
|
||||||
case 'dev':
|
|
||||||
case 'stg':
|
case 'stg':
|
||||||
this.redirect_uri = `https://maestro.${process.env.ENV}.dadosfera.ai/oauth`;
|
this.redirect_uri = `https://maestro.${process.env.ENV}.dadosfera.ai/oauth`;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class HubspotStrategy extends PassportStrategy(Strategy) {
|
|||||||
clientSecret: oauthSecrets.hubspot.client_secret,
|
clientSecret: oauthSecrets.hubspot.client_secret,
|
||||||
callbackURL: oauthSecrets.hubspot.redirect_uri,
|
callbackURL: oauthSecrets.hubspot.redirect_uri,
|
||||||
scope:
|
scope:
|
||||||
'automation business-intelligence oauth forms integration-sync sales-email-read crm.lists.read crm.objects.contacts.read crm.schemas.contacts.read crm.objects.companies.read crm.objects.deals.read crm.schemas.companies.read crm.schemas.deals.read crm.objects.owners.read crm.objects.quotes.read crm.schemas.quotes.read crm.objects.line_items.read crm.schemas.line_items.read',
|
'tickets automation business-intelligence oauth forms integration-sync sales-email-read crm.lists.read crm.objects.contacts.read crm.schemas.contacts.read crm.objects.companies.read crm.objects.deals.read crm.schemas.companies.read crm.schemas.deals.read crm.objects.owners.read crm.objects.quotes.read crm.schemas.quotes.read crm.objects.line_items.read crm.schemas.line_items.read',
|
||||||
passReqToCallback: true,
|
passReqToCallback: true,
|
||||||
},
|
},
|
||||||
(accessToken, refreshToken, tokenInfo, profile, done) => {
|
(accessToken, refreshToken, tokenInfo, profile, done) => {
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export class CreateUserOpenDataDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
firstName: string;
|
||||||
|
@ApiProperty()
|
||||||
|
lastName: string;
|
||||||
|
@ApiProperty()
|
||||||
|
email: string;
|
||||||
|
@ApiProperty()
|
||||||
|
organization: string;
|
||||||
|
@ApiProperty()
|
||||||
|
enquiryType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormField = {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
raw_value: string;
|
||||||
|
required: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MetaData = {
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WordpressForm = {
|
||||||
|
form: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
fields: {
|
||||||
|
[key: string]: FormField;
|
||||||
|
};
|
||||||
|
meta: {
|
||||||
|
date: MetaData;
|
||||||
|
time: MetaData;
|
||||||
|
page_url: MetaData;
|
||||||
|
user_agent: MetaData;
|
||||||
|
remote_ip: MetaData;
|
||||||
|
credit: MetaData;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||||
|
import { Body, Controller, Header, HttpCode, Inject, Param, Post, Query, Req, UseFilters, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiCreatedResponse, ApiHeaders, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { UsersService } from '../users/users.service';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { OpenDataService } from './open-data.service';
|
||||||
|
import { CreateUserOpenDataDTO, WordpressForm } from './dto/wordpres-form';
|
||||||
|
import { CORSGuard, SetOrigin } from 'src/decorators/set-origin.decorator';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
import { request } from 'http';
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
|
@Controller('open-data')
|
||||||
|
@ApiInternalOnlyController()
|
||||||
|
@ApiTags('OpenData')
|
||||||
|
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||||
|
@UseFilters(GrpcToHttpExceptionFilter)
|
||||||
|
export class OpenDataController {
|
||||||
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
dadosferaLogger: DadosferaLogger,
|
||||||
|
private openDataService: OpenDataService,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/sharing-ocean-data")
|
||||||
|
// @SetOrigin('devsbm.dadosfera.io')
|
||||||
|
// @UseGuards(CORSGuard)
|
||||||
|
@HttpCode(200)
|
||||||
|
@Header('content-type', 'application/json')
|
||||||
|
@ApiOkResponse()
|
||||||
|
async createUser(
|
||||||
|
@Body()
|
||||||
|
body: WordpressForm,
|
||||||
|
@Query('language')
|
||||||
|
language: string,
|
||||||
|
@Req()
|
||||||
|
request: Request,
|
||||||
|
) {
|
||||||
|
this.logger.info('createUser for open data'
|
||||||
|
+ JSON.stringify({
|
||||||
|
origin: request.headers.origin,
|
||||||
|
language,
|
||||||
|
body
|
||||||
|
}));
|
||||||
|
|
||||||
|
// "401573bb-334f-44b2-b30e-88d4cea31ae9"
|
||||||
|
// const OPENDATA_PUBLIC_USERS_GROUP_ID = process.env.OPEN_GROUP_ID;
|
||||||
|
// ""f239718a-a271-4ef9-ae7e-02a2f0f3aa6e""
|
||||||
|
// const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID;
|
||||||
|
const OPENDATA_CUSTOMER_ID = "b3e3dfe5-b992-4586-a73c-c0b0c00f615d";
|
||||||
|
|
||||||
|
this.logger.info("OPENDATA_CUSTOMER_ID: " + process.env.OPEN_CUSTOMER_ID)
|
||||||
|
this.logger.info("OPEN_GROUP_ID: " + process.env.OPEN_GROUP_ID)
|
||||||
|
const roles = ["e3f98a2f-7748-4981-8505-7695c8ca8218"];
|
||||||
|
const metadata = PackTheMetadata({
|
||||||
|
language: language || 'en-us'
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.keys(body.fields)
|
||||||
|
.filter(key => body.fields[key].required === "1")
|
||||||
|
.forEach(key => {
|
||||||
|
const field = body.fields[key]
|
||||||
|
data[field.id] = field.value
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('user data ' + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const user: CreateUserOpenDataDTO = {
|
||||||
|
email: data["email"],
|
||||||
|
enquiryType: data["enquiry_type"],
|
||||||
|
firstName: data["first_name"],
|
||||||
|
lastName: data["last_name"],
|
||||||
|
organization: data["organization"]
|
||||||
|
}
|
||||||
|
this.logger.info('user request' + JSON.stringify({ user, roles, customer: OPENDATA_CUSTOMER_ID }));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.openDataService.createUser(OPENDATA_CUSTOMER_ID, user, roles, metadata);
|
||||||
|
this.logger.info('user created with sucessfull data');
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: 'success',
|
||||||
|
message: 'user created with succesfull'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('failed with exception: ' + e.message);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
status: 'failed',
|
||||||
|
message: e.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { OpenDataController } from './open-data.controller';
|
||||||
|
import { UsersService } from '../users/users.service';
|
||||||
|
import { ClientsModule } from '@nestjs/microservices'
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||||
|
import { RolesModule } from '../roles/roles.module';
|
||||||
|
import { PermissionsModule } from '../permissions/permissions.module';
|
||||||
|
import { OpenDataService } from './open-data.service';
|
||||||
|
|
||||||
|
const client = new DucClient();
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [OpenDataController],
|
||||||
|
imports: [
|
||||||
|
ClientsModule.register([client.providerOptions]),
|
||||||
|
RolesModule,
|
||||||
|
// PermissionsModule,
|
||||||
|
],
|
||||||
|
providers: [DadosferaLogger, UsersService, OpenDataService]
|
||||||
|
})
|
||||||
|
export class OpenDataModule {}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { CreateUserOpenDataDTO } from './dto/wordpres-form';
|
||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||||
|
import { UsersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
|
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OpenDataService implements OnModuleInit {
|
||||||
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
|
private usersClientService: UsersProtoService;
|
||||||
|
constructor(
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
private dadosferaLogger: DadosferaLogger,
|
||||||
|
@Inject(DucClient.name)
|
||||||
|
private readonly grpcClient: ClientGrpc,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
this.usersClientService = this.grpcClient.getService(
|
||||||
|
ProtoServices.UsersProtoService,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(customerId: string, data: CreateUserOpenDataDTO, roleIds: string[], metadata: Metadata) {
|
||||||
|
const body = {
|
||||||
|
email: data.email,
|
||||||
|
name: data.firstName + " " + data.lastName,
|
||||||
|
department: data.organization,
|
||||||
|
jobTitle: data.enquiryType,
|
||||||
|
customerId: customerId,
|
||||||
|
roleIds: roleIds
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await lastValueFrom(
|
||||||
|
this.usersClientService.SimpleUserCreate(body, metadata),
|
||||||
|
);
|
||||||
|
return "User created";
|
||||||
|
} catch(err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
import { Controller, Get, Headers, Inject } from '@nestjs/common';
|
import { Controller, Get, Inject } from '@nestjs/common';
|
||||||
import { ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import {
|
import { Authenticated } from 'src/decorators/authentication.decorator';
|
||||||
Authenticated,
|
import { PermissionUsages } from 'src/authentication/permissions.enum';
|
||||||
RequireAllPermissions,
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
} from 'src/authentication/authentication.decorator';
|
|
||||||
import {
|
|
||||||
PERMISSIONS_GROUPS,
|
|
||||||
PermissionUsages,
|
|
||||||
} from 'src/authentication/permissions.enum';
|
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
|
||||||
import { LanguageEnum } from 'src/utils/languages.enum';
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
import { GetPublicPermissionsRes } from './dto/entities';
|
import { GetPublicPermissionsRes } from './dto/entities';
|
||||||
import { PermissionsService } from './permissions.service';
|
import { PermissionsService } from './permissions.service';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
|
@ApiInternalOnlyController()
|
||||||
@ApiTags('Permissions')
|
@ApiTags('Permissions')
|
||||||
|
@Authenticated()
|
||||||
@Controller('permissions')
|
@Controller('permissions')
|
||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: 'dadosfera-lang',
|
name: 'dadosfera-lang',
|
||||||
@@ -22,7 +20,6 @@ import { PermissionsService } from './permissions.service';
|
|||||||
required: false,
|
required: false,
|
||||||
description: 'Defaults to pt-br',
|
description: 'Defaults to pt-br',
|
||||||
})
|
})
|
||||||
@Authenticated()
|
|
||||||
export class PermissionsController {
|
export class PermissionsController {
|
||||||
logger: DadosferaLogger;
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
@@ -36,13 +33,12 @@ export class PermissionsController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOkResponse({ type: GetPublicPermissionsRes })
|
@ApiOkResponse({ type: GetPublicPermissionsRes })
|
||||||
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
|
||||||
getPublicPermissions(
|
getPublicPermissions(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('dadosfera-lang') language,
|
@Language() language: LanguageEnum,
|
||||||
) {
|
) {
|
||||||
this.logger.info('getPublicPermissions', { user });
|
this.logger.info('getPublicPermissions', { user });
|
||||||
if (!language) language = 'en-us';
|
|
||||||
const permissionGroups = this.permissionsService.getPermissionsGrouped(
|
const permissionGroups = this.permissionsService.getPermissionsGrouped(
|
||||||
PermissionUsages.PUBLIC,
|
PermissionUsages.PUBLIC,
|
||||||
language,
|
language,
|
||||||
|
|||||||
@@ -44,8 +44,11 @@ export class PermissionsService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// internally used to send permissions to duc on microservice startup
|
|
||||||
async onApplicationBootstrap() {
|
async onApplicationBootstrap() {
|
||||||
|
// Do not sent permissions if running local but pointing to a remote DUC url
|
||||||
|
if (process.env.ENV == 'local' && !process.env.DUC_URL.includes('0.0.0.0'))
|
||||||
|
return;
|
||||||
|
|
||||||
this.logger.info('sending permissions to DUC...');
|
this.logger.info('sending permissions to DUC...');
|
||||||
|
|
||||||
const permissions = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
const permissions = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||||
@@ -57,10 +60,10 @@ export class PermissionsService
|
|||||||
}).catch((err: ErrorBuilder) => {
|
}).catch((err: ErrorBuilder) => {
|
||||||
if (
|
if (
|
||||||
err.code === 'No connection established' &&
|
err.code === 'No connection established' &&
|
||||||
(process.env.LOCAL_ENV === 'true' || process.env.ENV === 'local')
|
process.env.ENV === 'local'
|
||||||
) {
|
) {
|
||||||
return this.logger.info(
|
return this.logger.info(
|
||||||
"couldn't connect to DUC. suppresing in local env",
|
"couldn't connect to DUC. suppressing in local env",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
|
import { ConflictException, Inject, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
PipelineServicesNames,
|
||||||
ConflictException,
|
PipelinesServiceInterface,
|
||||||
Inject,
|
} from '@dadosfera/protospack';
|
||||||
InternalServerErrorException,
|
|
||||||
OnModuleInit,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ClientGrpc, Payload } from '@nestjs/microservices';
|
|
||||||
import { PipelineServicesNames, PipelinesServiceInterface } from 'protospack';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { objectSnakeToCamel } from 'src/utils/CaseConverter';
|
|
||||||
|
|
||||||
import {
|
import { IIdRequest } from './interfaces';
|
||||||
ICreatePipelineDto,
|
|
||||||
IGetPipelineLogsRequest,
|
|
||||||
IIdRequest,
|
|
||||||
IUpdatePipelineRequest,
|
|
||||||
} from './interfaces';
|
|
||||||
|
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { PipelinesClientConfiguration } from './pipelines-client';
|
import { PipelinesClientConfiguration } from './pipelines-client';
|
||||||
@@ -23,6 +14,7 @@ import { PipelinesClientConfiguration } from './pipelines-client';
|
|||||||
export class PipelinesClientService implements OnModuleInit {
|
export class PipelinesClientService implements OnModuleInit {
|
||||||
private pipelineService: PipelinesServiceInterface;
|
private pipelineService: PipelinesServiceInterface;
|
||||||
logger: DadosferaLogger;
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DadosferaLogger)
|
@Inject(DadosferaLogger)
|
||||||
dadosferaLogger: DadosferaLogger,
|
dadosferaLogger: DadosferaLogger,
|
||||||
@@ -39,91 +31,6 @@ export class PipelinesClientService implements OnModuleInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(@Payload() createPipelineDto: ICreatePipelineDto) {
|
|
||||||
this.logger.info('PipelinesClientService - Create');
|
|
||||||
const createPipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.Create(objectSnakeToCamel(createPipelineDto)),
|
|
||||||
).catch((error: { details: string }) => {
|
|
||||||
if (error.details.includes('INVALID_REQUEST')) {
|
|
||||||
const [, message] = error.details.split('|');
|
|
||||||
throw new BadRequestException(message);
|
|
||||||
}
|
|
||||||
throw new InternalServerErrorException(error.details);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return createPipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(data) {
|
|
||||||
this.logger.info('PipelinesClientService - FindAll');
|
|
||||||
const findAllPipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.FindAll(data),
|
|
||||||
).catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return findAllPipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(data: IIdRequest) {
|
|
||||||
this.logger.info('PipelinesClientService - FindOne');
|
|
||||||
|
|
||||||
const findOnePipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.FindOne(data),
|
|
||||||
).catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return findOnePipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(UpdatePipelineRequest: IUpdatePipelineRequest) {
|
|
||||||
this.logger.info('PipelinesClientService - Update');
|
|
||||||
|
|
||||||
const updatePipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.Update(UpdatePipelineRequest),
|
|
||||||
).catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return updatePipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(data: IIdRequest) {
|
|
||||||
this.logger.info('PipelinesClientService - Remove');
|
|
||||||
|
|
||||||
const removePipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.remove(data),
|
|
||||||
).catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return removePipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPipelineLogsMessages(data: IGetPipelineLogsRequest) {
|
|
||||||
this.logger.info('PipelinesClientService - GetPipelineLogsMessages');
|
|
||||||
|
|
||||||
const logsPipelineResponse = await lastValueFrom(
|
|
||||||
this.pipelineService.getPipelineLogs(data),
|
|
||||||
).catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
|
||||||
|
|
||||||
return logsPipelineResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPipelineStatus(data) {
|
async getPipelineStatus(data) {
|
||||||
this.logger.info('PipelinesClientService - GetPipelineStatus');
|
this.logger.info('PipelinesClientService - GetPipelineStatus');
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { Info } from 'protospack/dist/lib/interfaces';
|
import { Info } from '@dadosfera/protospack/dist/lib/interfaces';
|
||||||
|
|
||||||
export interface ICreatePipelineDto {
|
export interface ICreatePipelineDto {
|
||||||
input: IdRequest;
|
input: IdRequest;
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ import {
|
|||||||
GrpcOptions,
|
GrpcOptions,
|
||||||
Transport,
|
Transport,
|
||||||
} from '@nestjs/microservices';
|
} from '@nestjs/microservices';
|
||||||
import { PipelinePackages, PipelineProtoFilePath } from 'protospack';
|
import { PipelinePackages, PipelineProtoFilePath } from '@dadosfera/protospack';
|
||||||
import { credentials } from '@grpc/grpc-js';
|
import { credentials } from '@grpc/grpc-js';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
|
||||||
|
process.env.PIFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class PipelinesClientConfiguration {
|
export class PipelinesClientConfiguration {
|
||||||
public name = 'PipelinesClientConfiguration';
|
public name = 'PipelinesClientConfiguration';
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
@@ -13,10 +17,7 @@ export class PipelinesClientConfiguration {
|
|||||||
options: {
|
options: {
|
||||||
url: process.env.PIFACTORY_URL,
|
url: process.env.PIFACTORY_URL,
|
||||||
package: PipelinePackages,
|
package: PipelinePackages,
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: PipelineProtoFilePath,
|
protoPath: PipelineProtoFilePath,
|
||||||
loader: {
|
loader: {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
|
import { Body, Controller, Get, Inject, Param, Post } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import {
|
import {
|
||||||
Body,
|
AuthenticateCondition,
|
||||||
Controller,
|
Authenticated,
|
||||||
Delete,
|
} from 'src/decorators/authentication.decorator';
|
||||||
Get,
|
|
||||||
Inject,
|
|
||||||
Param,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
|
|
||||||
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
||||||
import { PipelinesService } from './pipelines.service';
|
import { PipelinesService } from './pipelines.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
|
@ApiInternalOnlyController()
|
||||||
@ApiTags('Pipelines')
|
@ApiTags('Pipelines')
|
||||||
@Controller('pipelines')
|
@Controller('pipelines')
|
||||||
|
@Authenticated()
|
||||||
@AuthenticateCondition((req, user) => {
|
@AuthenticateCondition((req, user) => {
|
||||||
let action;
|
let action;
|
||||||
|
|
||||||
@@ -48,8 +44,12 @@ export class PipelinesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('start/:id')
|
@Post('start/:id')
|
||||||
async activate(@Param() params, @Body() body) {
|
@ApiOperation({
|
||||||
const { id } = params;
|
deprecated: true,
|
||||||
|
description:
|
||||||
|
'This method is deprecated. Please use route /pipelinesV2/start/:id instead',
|
||||||
|
})
|
||||||
|
async activate(@Param('id') id: string, @Body() body) {
|
||||||
const { info } = body;
|
const { info } = body;
|
||||||
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -66,8 +66,12 @@ export class PipelinesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/status')
|
@Get(':id/status')
|
||||||
async getPipelineStatus(@Body() body, @Param() params) {
|
@ApiOperation({
|
||||||
const { id } = params;
|
deprecated: true,
|
||||||
|
description:
|
||||||
|
'This method is deprecated. Please use route /pipelinesV2/:id/status instead',
|
||||||
|
})
|
||||||
|
async getPipelineStatus(@Body() body, @Param('id') id: string) {
|
||||||
body.id = id;
|
body.id = id;
|
||||||
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
@@ -82,84 +86,4 @@ export class PipelinesController {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/:details')
|
|
||||||
async getPipelineLogs(@Param() params) {
|
|
||||||
const { id, details } = params;
|
|
||||||
this.logger.info(
|
|
||||||
process.env.DEV_URL + `/pipeline/${id} - ON GET PIPELINE LOGS ROUTE`,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await this.pipelineService.getPipelineLogsMessages(
|
|
||||||
id,
|
|
||||||
details,
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
async create(@Body() createPipelineDto) {
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipelines ON CREATE ROUTE`);
|
|
||||||
|
|
||||||
const response = await this.pipelineService.create(createPipelineDto);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
async findAll(@User() user: RequestUser) {
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline ON Find All ROUTE`, {
|
|
||||||
user: user.user_id,
|
|
||||||
customer: user.customer_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { customer_id, user_id, customer_name } = user;
|
|
||||||
|
|
||||||
const response = await this.pipelineService.findAll({
|
|
||||||
customer_name,
|
|
||||||
customer_id,
|
|
||||||
user_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
async findOne(@Body() data, @Param() params) {
|
|
||||||
const { id } = params;
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON Find One ROUTE`);
|
|
||||||
|
|
||||||
const response = await this.pipelineService.findOne({ id, ...data });
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put(':id')
|
|
||||||
async update(@Body() updatePipelineDto, @Param() params) {
|
|
||||||
const { id } = params;
|
|
||||||
const { info } = updatePipelineDto;
|
|
||||||
delete updatePipelineDto.info;
|
|
||||||
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON UPDATE ROUTE`);
|
|
||||||
|
|
||||||
const response = await this.pipelineService.update(
|
|
||||||
id,
|
|
||||||
updatePipelineDto,
|
|
||||||
info,
|
|
||||||
);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(':id')
|
|
||||||
async delete(@Body() data, @Param() params) {
|
|
||||||
const { id } = params;
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON DELETE ROUTE`);
|
|
||||||
|
|
||||||
const response = await this.pipelineService.remove({ id, ...data });
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { DecodeGrpcStruct } from 'protospack';
|
|
||||||
import { Info } from 'protospack/dist/lib/interfaces';
|
|
||||||
import { PipelinesClientService } from './client.service';
|
import { PipelinesClientService } from './client.service';
|
||||||
import { IIdRequest } from './interfaces';
|
import { IIdRequest } from './interfaces';
|
||||||
import { objectCamelToSnake } from 'src/utils/CaseConverter';
|
import { objectCamelToSnake } from 'src/utils/CaseConverter';
|
||||||
@@ -9,91 +7,6 @@ import { objectCamelToSnake } from 'src/utils/CaseConverter';
|
|||||||
export class PipelinesService {
|
export class PipelinesService {
|
||||||
constructor(private pipelineClient: PipelinesClientService) {}
|
constructor(private pipelineClient: PipelinesClientService) {}
|
||||||
|
|
||||||
adjustPayload(payload) {
|
|
||||||
if (payload.input?.input_generic) {
|
|
||||||
payload.input = DecodeGrpcStruct(payload.input?.input_generic);
|
|
||||||
} else {
|
|
||||||
payload.input = payload.input?.input_s3 || payload.input?.input_jdbc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(createPipelineDto) {
|
|
||||||
const createPipelineResponse = await this.pipelineClient.create(
|
|
||||||
createPipelineDto,
|
|
||||||
);
|
|
||||||
|
|
||||||
const pipeline = objectCamelToSnake(createPipelineResponse);
|
|
||||||
this.adjustPayload(pipeline.pipeline);
|
|
||||||
|
|
||||||
return pipeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(data: IIdRequest) {
|
|
||||||
try {
|
|
||||||
const findOnePipelineResponse = await this.pipelineClient.findOne(data);
|
|
||||||
|
|
||||||
const pipeline = objectCamelToSnake(findOnePipelineResponse);
|
|
||||||
this.adjustPayload(pipeline.pipeline);
|
|
||||||
|
|
||||||
return pipeline;
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(data) {
|
|
||||||
try {
|
|
||||||
const { pipelines } = await this.pipelineClient.findAll({
|
|
||||||
info: data,
|
|
||||||
});
|
|
||||||
if (pipelines)
|
|
||||||
pipelines.forEach((pipeline) => {
|
|
||||||
this.adjustPayload(pipeline);
|
|
||||||
});
|
|
||||||
return { pipelines: pipelines || [] };
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(id: string, data, info: Info) {
|
|
||||||
try {
|
|
||||||
const updatePipelineResponse = await this.pipelineClient.update({
|
|
||||||
id,
|
|
||||||
info,
|
|
||||||
...data,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pipeline = objectCamelToSnake(updatePipelineResponse);
|
|
||||||
this.adjustPayload(pipeline);
|
|
||||||
|
|
||||||
return pipeline;
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(data: IIdRequest) {
|
|
||||||
try {
|
|
||||||
const removePipelineResponse = await this.pipelineClient.remove(data);
|
|
||||||
|
|
||||||
return removePipelineResponse;
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPipelineLogsMessages(id: string, details: string) {
|
|
||||||
try {
|
|
||||||
const pipelineLogsResponse =
|
|
||||||
await this.pipelineClient.getPipelineLogsMessages({ id, details });
|
|
||||||
|
|
||||||
return objectCamelToSnake(pipelineLogsResponse);
|
|
||||||
} catch (err) {
|
|
||||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPipelineStatus(data: IIdRequest) {
|
async getPipelineStatus(data: IIdRequest) {
|
||||||
try {
|
try {
|
||||||
const pipelineStatusResponse =
|
const pipelineStatusResponse =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
||||||
import { Info } from 'protospack/dist/lib/interfaces';
|
import { Info } from '@dadosfera/protospack/dist/lib/interfaces';
|
||||||
|
|
||||||
export class IPipelineV2 {
|
export class IPipelineV2 {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -10,6 +10,8 @@ export class IPipelineV2 {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
description: string;
|
description: string;
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
type: string;
|
||||||
|
@ApiProperty()
|
||||||
connection_id: string;
|
connection_id: string;
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
cron: string;
|
cron: string;
|
||||||
@@ -66,3 +68,58 @@ export interface IGetPipelineLogsRequest {
|
|||||||
id: string;
|
id: string;
|
||||||
details: string;
|
details: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class IInitUploadCSVFile {
|
||||||
|
@ApiProperty()
|
||||||
|
file_name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
parts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ICompleteUploadCSVFile {
|
||||||
|
@ApiProperty()
|
||||||
|
upload_id: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
file_name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
parts: { ETag: string; PartNumber: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ICreatePipelineCSVFile {
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
file_name: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
encoding: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
sep: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
header: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
engine: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PipelineFindAllReq {
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
search?: string | undefined;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
filters?: string | undefined;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
size?: string | undefined;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
page?: string | undefined;
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
type?: string | undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import {
|
|||||||
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
|
} from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
|
||||||
import { credentials } from '@grpc/grpc-js';
|
import { credentials } from '@grpc/grpc-js';
|
||||||
|
|
||||||
|
const isLocalConnection =
|
||||||
|
process.env.PIFACTORY_URL.startsWith('pi-factory:') ||
|
||||||
|
process.env.PIFACTORY_URL.includes('0.0.0.0');
|
||||||
|
|
||||||
export class PipelinesClientConfiguration {
|
export class PipelinesClientConfiguration {
|
||||||
public name = 'PipelinesClientConfiguration';
|
public name = 'PipelinesClientConfiguration';
|
||||||
private config: GrpcOptions = {
|
private config: GrpcOptions = {
|
||||||
@@ -16,10 +20,7 @@ export class PipelinesClientConfiguration {
|
|||||||
options: {
|
options: {
|
||||||
url: process.env.PIFACTORY_URL,
|
url: process.env.PIFACTORY_URL,
|
||||||
package: [ProtoPackages.ReadPackage, ProtoPackages.WritePackage],
|
package: [ProtoPackages.ReadPackage, ProtoPackages.WritePackage],
|
||||||
credentials:
|
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||||
process.env.LOCAL_ENV || process.env.ENV === 'local'
|
|
||||||
? undefined
|
|
||||||
: credentials.createSsl(),
|
|
||||||
protoPath: [ProtoPaths.ReadFilePath, ProtoPaths.WriteFilePath],
|
protoPath: [ProtoPaths.ReadFilePath, ProtoPaths.WriteFilePath],
|
||||||
loader: {
|
loader: {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
|
|||||||
@@ -7,21 +7,50 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
Headers,
|
|
||||||
Query,
|
Query,
|
||||||
|
UseFilters,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Patch,
|
||||||
|
HttpException,
|
||||||
|
BadRequestException,
|
||||||
|
CacheTTL,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
|
import {
|
||||||
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
|
ApiCreatedResponse,
|
||||||
|
ApiHeaders,
|
||||||
|
ApiNoContentResponse,
|
||||||
|
ApiOperation,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
AuthenticateCondition,
|
||||||
|
RequireAllPermissions,
|
||||||
|
} from 'src/decorators/authentication.decorator';
|
||||||
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
||||||
import { PipelinesService } from './pipelines.service';
|
import { PipelinesService } from './pipelines.service';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
import { Messages } from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
|
import { Messages } from '@dadosfera/protospack-v2/dist/lib/PipelineV2';
|
||||||
import { RequestUser, User } from 'src/authentication/user.decorator';
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||||
|
|
||||||
import { PipelinesService as OldPipelineService } from 'src/modules/pipelines/pipelines.service';
|
import { PipelinesService as OldPipelineService } from 'src/modules/pipelines/pipelines.service';
|
||||||
import { ICreatePipelineV2Req, IPipelineV2 } from './interfaces';
|
import {
|
||||||
@ApiTags('Pipelines')
|
ICompleteUploadCSVFile,
|
||||||
|
ICreatePipelineCSVFile,
|
||||||
|
ICreatePipelineV2Req,
|
||||||
|
IPipelineV2,
|
||||||
|
IInitUploadCSVFile,
|
||||||
|
PipelineFindAllReq,
|
||||||
|
} from './interfaces';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { Language } from 'src/decorators/language.decorator';
|
||||||
|
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||||
|
|
||||||
|
@ApiTags('PipelinesV2')
|
||||||
|
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||||
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
@Controller('pipelinesV2')
|
@Controller('pipelinesV2')
|
||||||
@AuthenticateCondition((req, user) => {
|
@AuthenticateCondition((req, user) => {
|
||||||
let action;
|
let action;
|
||||||
@@ -35,6 +64,10 @@ import { ICreatePipelineV2Req, IPipelineV2 } from './interfaces';
|
|||||||
action = 'UPDATE';
|
action = 'UPDATE';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'PATCH':
|
||||||
|
action = 'UPDATE';
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
action = req.method;
|
action = req.method;
|
||||||
}
|
}
|
||||||
@@ -54,15 +87,27 @@ export class PipelinesController {
|
|||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('monitoring-dashboard')
|
||||||
|
async getMonitoringDashboard(@User() user: RequestUser) {
|
||||||
|
this.logger.info('PipelinesController - getMonitoringDashboard', { user });
|
||||||
|
|
||||||
|
const metadata = PackTheMetadata(user);
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await this.pipelinesClientService.getMonitoringDashboardUrl(metadata);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ApiCreatedResponse({ type: IPipelineV2 })
|
@ApiCreatedResponse({ type: IPipelineV2 })
|
||||||
async create(
|
async create(
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Body() createPipelineDto: ICreatePipelineV2Req,
|
@Body() createPipelineDto: ICreatePipelineV2Req,
|
||||||
) {
|
) {
|
||||||
this.logger.info(process.env.DEV_URL + `/pipelines ON CREATE ROUTE`);
|
this.logger.info('PipelinesController - create', { user });
|
||||||
if (!language) language = 'en-us';
|
|
||||||
const { customer_id, customer_name, username, user_id } = user;
|
const { customer_id, customer_name, username, user_id } = user;
|
||||||
const metadata = PackTheMetadata({
|
const metadata = PackTheMetadata({
|
||||||
customer_id,
|
customer_id,
|
||||||
@@ -83,15 +128,10 @@ export class PipelinesController {
|
|||||||
@Get()
|
@Get()
|
||||||
async findAll(
|
async findAll(
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@Query() data,
|
@Query() data: PipelineFindAllReq,
|
||||||
) {
|
) {
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline ON Find All ROUTE`, {
|
this.logger.info('PipelinesController - findAll', { user });
|
||||||
user: user.user_id,
|
|
||||||
customer: user.customer_name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { customer_id, customer_name, user_id, username } = user;
|
const { customer_id, customer_name, user_id, username } = user;
|
||||||
const metadata = PackTheMetadata({
|
const metadata = PackTheMetadata({
|
||||||
@@ -103,25 +143,88 @@ export class PipelinesController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await this.pipelinesClientService.findAll(data, metadata);
|
const response = await this.pipelinesClientService.findAll(data, metadata);
|
||||||
const oldResponse = await this.oldPipelinesService.findAll({
|
|
||||||
customer_name,
|
|
||||||
user_id,
|
|
||||||
customer_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pipelines: [...response.pipelines, ...oldResponse.pipelines],
|
pipelines: [...response.pipelines],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/download-logs')
|
||||||
|
async downloadLogs(
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
@Query('pipeline_run_id') pipeline_run_id: string,
|
||||||
|
) {
|
||||||
|
this.logger.info('PipelinesController - downloadLogs', { user });
|
||||||
|
|
||||||
|
if (!pipeline_run_id)
|
||||||
|
throw new BadRequestException('Missing pipeline_run_id');
|
||||||
|
|
||||||
|
const { customer_id, customer_name, user_id, username } = user;
|
||||||
|
const metadata = PackTheMetadata({
|
||||||
|
customer_id,
|
||||||
|
customer_name,
|
||||||
|
user_id,
|
||||||
|
username,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = await this.pipelinesClientService.downloadLogs(
|
||||||
|
pipeline_run_id,
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/config')
|
||||||
|
async getPipelineproperties(
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
this.logger.info('PipelinesController - getPipelineproperties', { user });
|
||||||
|
const metadata = PackTheMetadata({ ...user, language: language });
|
||||||
|
return this.pipelinesClientService.findOneProperties(id, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/objects')
|
||||||
|
async getPipelineObjects(
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
this.logger.info('PipelinesController - getPipelineObjects', { user });
|
||||||
|
const metadata = PackTheMetadata({ ...user, language: language });
|
||||||
|
return this.pipelinesClientService.findOneObjects(id, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id/status')
|
||||||
|
async getPipelineStatus(@Body() body, @Param('id') id: string) {
|
||||||
|
body.id = id;
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
process.env.DEV_URL + `/pipeline/${id} - ON GET PIPELINE STATUS ROUTE`,
|
||||||
|
{
|
||||||
|
user: body.info.user_id,
|
||||||
|
customer: body.info.customer,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await this.oldPipelinesService.getPipelineStatus(body);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
@Get('/:id')
|
@Get('/:id')
|
||||||
async findOne(
|
async findOne(
|
||||||
@Headers('Dadosfera-Lang') language,
|
@Language() language: LanguageEnum,
|
||||||
@User() user: RequestUser,
|
@User() user: RequestUser,
|
||||||
@Param('id') id,
|
@Param('id') id: string,
|
||||||
): Promise<Messages.PipelineV2FindOneResponse> {
|
): Promise<Messages.PipelineV2FindOneResponse> {
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON Find One ROUTE`);
|
this.logger.info('PipelinesController - findOne', { user });
|
||||||
if (!language) language = 'en-us';
|
|
||||||
|
|
||||||
const { customer_id, customer_name, user_id, username } = user;
|
const { customer_id, customer_name, user_id, username } = user;
|
||||||
|
|
||||||
@@ -136,52 +239,206 @@ export class PipelinesController {
|
|||||||
const result = await this.pipelinesClientService
|
const result = await this.pipelinesClientService
|
||||||
.findOne({ id }, metadata)
|
.findOne({ id }, metadata)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
//{pipeline:{tables: {tables: [], input_id: ''}}}
|
||||||
|
let tables = JSON.parse(res.pipeline.config.tables);
|
||||||
|
if (tables?.tables) tables = tables.tables;
|
||||||
Object.assign(res.pipeline, {
|
Object.assign(res.pipeline, {
|
||||||
transformations: res.pipeline.transformations
|
transformations: res.pipeline.transformations
|
||||||
? JSON.parse(res.pipeline.transformations)
|
? JSON.parse(res.pipeline.transformations)
|
||||||
: [],
|
: [],
|
||||||
config: {
|
config: {
|
||||||
cron: res.pipeline.config.cron,
|
cron: res.pipeline.config.cron,
|
||||||
tables: JSON.parse(res.pipeline.config.tables),
|
tables,
|
||||||
},
|
},
|
||||||
properties: res.pipeline.properties
|
properties: res.pipeline.properties
|
||||||
? JSON.parse(res.pipeline.properties)
|
? JSON.parse(res.pipeline.properties)
|
||||||
: {},
|
: {},
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
})
|
|
||||||
.catch(async (err) => {
|
|
||||||
this.logger.error(err);
|
|
||||||
return await this.oldPipelinesService.findOne({
|
|
||||||
id,
|
|
||||||
info: { customer_id, customer: customer_name, user_id },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('/:id')
|
@Patch('/:id')
|
||||||
async update(@Body() updatePipelineDto, @Param('id') id) {
|
async update(
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
@Body() updatePipelineDto,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@User() user: RequestUser,
|
||||||
|
) {
|
||||||
|
this.logger.info('PipelinesController - update', { user });
|
||||||
const { info } = updatePipelineDto;
|
const { info } = updatePipelineDto;
|
||||||
delete updatePipelineDto.info;
|
delete updatePipelineDto.info;
|
||||||
|
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON UPDATE ROUTE`);
|
const { customer_id, customer_name, user_id, username } = user;
|
||||||
|
|
||||||
const response = await this.pipelinesClientService.update({
|
const metadata = PackTheMetadata({
|
||||||
|
customer_id,
|
||||||
|
customer_name,
|
||||||
|
user_id,
|
||||||
|
username,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.pipelinesClientService.update(
|
||||||
|
{
|
||||||
...updatePipelineDto,
|
...updatePipelineDto,
|
||||||
info,
|
info,
|
||||||
id,
|
id,
|
||||||
});
|
},
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.info('PipelinesController - update: OK', { user });
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Put('/:id')
|
||||||
|
@ApiOperation({
|
||||||
|
deprecated: true,
|
||||||
|
description: 'This method is deprecated. Please use PATCH instead',
|
||||||
|
})
|
||||||
|
async updateDeprecated(
|
||||||
|
@Language() language: LanguageEnum,
|
||||||
|
@Body() updatePipelineDto,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@User() user: RequestUser,
|
||||||
|
) {
|
||||||
|
this.logger.info('PipelinesController - put', { user });
|
||||||
|
const response = await this.update(language, updatePipelineDto, id, user);
|
||||||
|
this.logger.info('PipelinesController - put: OK', { user });
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
async delete(@Body() data, @Param() params) {
|
@ApiNoContentResponse()
|
||||||
const { id } = params;
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
this.logger.info(process.env.DEV_URL + `/pipeline/${id} ON DELETE ROUTE`);
|
async delete(@Param('id') id: string, @User() user: RequestUser) {
|
||||||
|
this.logger.info('PipelinesController - delete', { user });
|
||||||
|
const metadata = PackTheMetadata({
|
||||||
|
customer_id: user.customer_id,
|
||||||
|
customer_name: user.customer_name,
|
||||||
|
user_id: user.user_id,
|
||||||
|
});
|
||||||
|
await this.pipelinesClientService.remove({ id, metadata, user });
|
||||||
|
this.logger.info('PipelinesController - delete: OK');
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.pipelinesClientService.remove({ id, ...data });
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Post('/init-upload')
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
|
||||||
|
async initUploadFile(
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Body() body: IInitUploadCSVFile,
|
||||||
|
) {
|
||||||
|
this.logger.info('/upload - Init Upload File Route');
|
||||||
|
const metadata = PackTheMetadata({ ...user });
|
||||||
|
|
||||||
|
const { file_name, parts } = body;
|
||||||
|
|
||||||
|
const { urls, upload_id } =
|
||||||
|
await this.pipelinesClientService.initUploadFile(
|
||||||
|
{
|
||||||
|
name: file_name,
|
||||||
|
parts: String(parts),
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!urls) {
|
||||||
|
this.logger.info('pipeline/upload - Failed File pipeline');
|
||||||
|
throw new HttpException(
|
||||||
|
'Upload failed, try again in a few minutes, if the problem persists, contact support.',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { urls: JSON.parse(urls), upload_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Post('/complete-upload')
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
|
||||||
|
async completeUploadFile(
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Body() body: ICompleteUploadCSVFile,
|
||||||
|
) {
|
||||||
|
this.logger.info('/upload - Complete Upload File Route');
|
||||||
|
const metadata = PackTheMetadata({ ...user });
|
||||||
|
|
||||||
|
const { upload_id, parts, file_name } = body;
|
||||||
|
|
||||||
|
return await this.pipelinesClientService.completeUploadFile(
|
||||||
|
{ upload_id, parts, file_name },
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Post('/file')
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE)
|
||||||
|
async uploadedFile(
|
||||||
|
@User() user: RequestUser,
|
||||||
|
@Body() body: ICreatePipelineCSVFile,
|
||||||
|
) {
|
||||||
|
this.logger.info('/file - Upload Connector Route');
|
||||||
|
const metadata = PackTheMetadata({ ...user });
|
||||||
|
|
||||||
|
const { name, sep, header, encoding, description, file_name, engine } =
|
||||||
|
body;
|
||||||
|
|
||||||
|
const file_format_params = {
|
||||||
|
...(sep && { sep }),
|
||||||
|
...(encoding && { encoding }),
|
||||||
|
...(header !== undefined ? { header } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const source_prefix = `${user.customer_name}/${file_name}`;
|
||||||
|
|
||||||
|
const upload_pipeline: ICreatePipelineV2Req = {
|
||||||
|
connection_id: process.env.UPLOAD_FILE_AGENT_CONNECTION,
|
||||||
|
connector_name: 'Amazon S3',
|
||||||
|
connector_plugin: 'aws_s3',
|
||||||
|
connector_version: '1.0.0',
|
||||||
|
image_url: `https://assets.dadosfera.ai/images/connectors/${engine}.svg`,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
transformations_ids: [],
|
||||||
|
tags: [],
|
||||||
|
cron: '@once',
|
||||||
|
type: 'upload',
|
||||||
|
properties: {
|
||||||
|
engine,
|
||||||
|
source_bucket: process.env.BUCKET_CUSTOMER_CSV_ASSETS,
|
||||||
|
source_prefix,
|
||||||
|
file_format_params,
|
||||||
|
},
|
||||||
|
input_id: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const pipeline = await this.pipelinesClientService.create(
|
||||||
|
upload_pipeline,
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiInternalOnlyEndpoint()
|
||||||
|
@Post('start/:id')
|
||||||
|
async activate(@Param('id') id: string, @Body() body) {
|
||||||
|
const { info } = body;
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
process.env.DEV_URL + `/pipeline/start/${id} - ON START PIPELINE ROUTE`,
|
||||||
|
{
|
||||||
|
user: body.info.user_id,
|
||||||
|
customer: body.info.customer,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await this.oldPipelinesService.runPipeline({ id, info });
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { PipelinesService } from './pipelines.service';
|
|||||||
import { PipelinesClientConfiguration } from './pipelines-client';
|
import { PipelinesClientConfiguration } from './pipelines-client';
|
||||||
|
|
||||||
import { PipelinesModule as OldPipelineModule } from 'src/modules/pipelines/pipelines.module';
|
import { PipelinesModule as OldPipelineModule } from 'src/modules/pipelines/pipelines.module';
|
||||||
|
import { ConnectorModule } from '../connector/connector.module';
|
||||||
|
import { InputsModule } from '../inputs/inputs.module';
|
||||||
|
import { TransformationsModule } from '../transformations/transformations.module';
|
||||||
|
|
||||||
const client = new PipelinesClientConfiguration();
|
const client = new PipelinesClientConfiguration();
|
||||||
|
|
||||||
@@ -15,6 +18,9 @@ const client = new PipelinesClientConfiguration();
|
|||||||
imports: [
|
imports: [
|
||||||
ClientsModule.register([client.providerOptions]),
|
ClientsModule.register([client.providerOptions]),
|
||||||
OldPipelineModule,
|
OldPipelineModule,
|
||||||
|
ConnectorModule,
|
||||||
|
InputsModule,
|
||||||
|
TransformationsModule,
|
||||||
],
|
],
|
||||||
controllers: [PipelinesController],
|
controllers: [PipelinesController],
|
||||||
providers: [PipelinesService, DadosferaLogger],
|
providers: [PipelinesService, DadosferaLogger],
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
Inject,
|
Inject,
|
||||||
InternalServerErrorException,
|
|
||||||
OnModuleInit,
|
OnModuleInit,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ClientGrpc } from '@nestjs/microservices';
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
@@ -17,6 +18,14 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
|||||||
import { PipelinesClientConfiguration } from './pipelines-client';
|
import { PipelinesClientConfiguration } from './pipelines-client';
|
||||||
import { ICreatePipelineV2Req } from './interfaces';
|
import { ICreatePipelineV2Req } from './interfaces';
|
||||||
import { PipelineV2CreateRequest } from '@dadosfera/protospack-v2/dist/lib/PipelineV2/interfaces/messages';
|
import { PipelineV2CreateRequest } from '@dadosfera/protospack-v2/dist/lib/PipelineV2/interfaces/messages';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
|
import { ConnectorClientService } from '../connector/client.service';
|
||||||
|
import { InputsService } from '../inputs/inputs.service';
|
||||||
|
import { RequestUser } from 'src/decorators/user.decorator';
|
||||||
|
import { TransformationsService } from '../transformations/transformations.service';
|
||||||
|
import { getObjValueFromPath, objHasPath } from 'src/utils/ObjValueFromPath';
|
||||||
|
import ErrorCodes from 'src/utils/errorCodes';
|
||||||
|
import ErrorBuilder from 'src/utils/ErrorBuilder';
|
||||||
|
|
||||||
export class PipelinesService implements OnModuleInit {
|
export class PipelinesService implements OnModuleInit {
|
||||||
logger: DadosferaLogger;
|
logger: DadosferaLogger;
|
||||||
@@ -27,6 +36,9 @@ export class PipelinesService implements OnModuleInit {
|
|||||||
dadosferaLogger: DadosferaLogger,
|
dadosferaLogger: DadosferaLogger,
|
||||||
@Inject(PipelinesClientConfiguration.name)
|
@Inject(PipelinesClientConfiguration.name)
|
||||||
private readonly grpcClient: ClientGrpc,
|
private readonly grpcClient: ClientGrpc,
|
||||||
|
private readonly connectorService: ConnectorClientService,
|
||||||
|
private readonly inputsService: InputsService,
|
||||||
|
private readonly transformationsService: TransformationsService,
|
||||||
) {
|
) {
|
||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
}
|
}
|
||||||
@@ -90,7 +102,7 @@ export class PipelinesService implements OnModuleInit {
|
|||||||
const [errorType, message] = error.details.split('|');
|
const [errorType, message] = error.details.split('|');
|
||||||
throw new BadRequestException(message);
|
throw new BadRequestException(message);
|
||||||
}
|
}
|
||||||
throw new InternalServerErrorException(error.details);
|
throw new ErrorBuilder(error.details);
|
||||||
});
|
});
|
||||||
this.logger.info('Done');
|
this.logger.info('Done');
|
||||||
|
|
||||||
@@ -125,48 +137,206 @@ export class PipelinesService implements OnModuleInit {
|
|||||||
|
|
||||||
const findOnePipelineResponse = await lastValueFrom(
|
const findOnePipelineResponse = await lastValueFrom(
|
||||||
this.pipelineReadService.PipelineV2FindOne(data, metadata),
|
this.pipelineReadService.PipelineV2FindOne(data, metadata),
|
||||||
).catch((err) => {
|
);
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
this.logger.info('Done');
|
this.logger.info('Done');
|
||||||
|
|
||||||
return findOnePipelineResponse;
|
return findOnePipelineResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(UpdatePipelineRequest: Messages.PipelineV2UpdateRequest) {
|
async update(
|
||||||
|
UpdatePipelineRequest: Messages.PipelineV2UpdateRequest,
|
||||||
|
metadata,
|
||||||
|
) {
|
||||||
this.logger.info('PipelinesClientService - Update');
|
this.logger.info('PipelinesClientService - Update');
|
||||||
|
|
||||||
const updatePipelineResponse = await lastValueFrom(
|
const updatePipelineResponse = await lastValueFrom(
|
||||||
this.pipelineWriteService.PipelineV2Update(UpdatePipelineRequest),
|
this.pipelineWriteService.PipelineV2Update(
|
||||||
)
|
UpdatePipelineRequest,
|
||||||
.then((res) => {
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
this.logger.info('Done');
|
this.logger.info('Done');
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
throw new Error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return updatePipelineResponse;
|
return updatePipelineResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(data: Messages.PipelineV2RemoveRequest) {
|
async remove(data: { id: string; metadata: Metadata; user: RequestUser }) {
|
||||||
this.logger.info('PipelinesClientService - Remove');
|
const { id, metadata, user } = data;
|
||||||
|
const info = {
|
||||||
|
user_id: user.user_id,
|
||||||
|
customer_id: user.customer_id,
|
||||||
|
customer: user.customer_name,
|
||||||
|
};
|
||||||
|
let existsInProduct = true;
|
||||||
|
const { pipeline } = await lastValueFrom(
|
||||||
|
this.pipelineReadService.PipelineV2FindOne({ id }, metadata),
|
||||||
|
).catch((e) => {
|
||||||
|
if (e.details === ErrorCodes.PIPELINE.NOT_FOUND) existsInProduct = false;
|
||||||
|
else throw new ErrorBuilder(e.details);
|
||||||
|
|
||||||
const removePipelineResponse = await lastValueFrom(
|
return { pipeline: undefined };
|
||||||
this.pipelineWriteService.PipelineV2Remove(data),
|
});
|
||||||
)
|
await lastValueFrom(
|
||||||
.then((res) => {
|
this.pipelineWriteService.PipelineV2Remove({ id }, metadata),
|
||||||
this.logger.info('Done');
|
);
|
||||||
return res;
|
|
||||||
|
if (existsInProduct) {
|
||||||
|
//{pipeline:{tables: {tables: [], input_id: ''}}}
|
||||||
|
const input = pipeline.config.tables
|
||||||
|
? JSON.parse(pipeline.config.tables)
|
||||||
|
: null;
|
||||||
|
if (input)
|
||||||
|
await this.inputsService
|
||||||
|
.remove({
|
||||||
|
id: input.input_id,
|
||||||
|
info,
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) =>
|
||||||
this.logger.error(err.message);
|
this.logger.error('Could not delete input', {
|
||||||
throw new Error(err);
|
data: { input, error },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const transformations: { id: string }[] = pipeline.transformations
|
||||||
|
? JSON.parse(pipeline.transformations)
|
||||||
|
: null;
|
||||||
|
if (transformations && transformations.length)
|
||||||
|
for (const transformation of transformations) {
|
||||||
|
await this.transformationsService
|
||||||
|
.remove({
|
||||||
|
id: transformation.id,
|
||||||
|
info,
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
this.logger.error('Could not delete transformation', {
|
||||||
|
data: { transformation, error },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneProperties(id: string, metadata: Metadata) {
|
||||||
|
this.logger.info('PipelinesClientService - findOneProperties');
|
||||||
|
const { pipeline } = await this.findOne({ id }, metadata);
|
||||||
|
|
||||||
|
const properties = JSON.parse(pipeline.properties);
|
||||||
|
|
||||||
|
const connectorId = `${pipeline.connector_plugin}-${pipeline.connector_version}`;
|
||||||
|
|
||||||
|
const res = await this.connectorService.getConnectorDetails(
|
||||||
|
connectorId,
|
||||||
|
metadata.get('language')[0].toString(),
|
||||||
|
);
|
||||||
|
const connector: { [key: string]: any; config_controls: any[] } =
|
||||||
|
JSON.parse(res.connector);
|
||||||
|
const config_controls = (connector.config_controls || []).filter(
|
||||||
|
(config_control) => {
|
||||||
|
const path: string = config_control.name;
|
||||||
|
if (!objHasPath({ object: { properties }, path })) return false;
|
||||||
|
|
||||||
|
let default_value = getObjValueFromPath({
|
||||||
|
object: { properties },
|
||||||
|
path,
|
||||||
});
|
});
|
||||||
|
|
||||||
return removePipelineResponse;
|
if (default_value != undefined) {
|
||||||
|
if (
|
||||||
|
path === 'properties.spreadsheet_id' &&
|
||||||
|
typeof default_value === 'string'
|
||||||
|
)
|
||||||
|
default_value = 'https://docs.google.com/spreadsheets/d/'.concat(
|
||||||
|
default_value,
|
||||||
|
);
|
||||||
|
config_control.default_value = default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pipeline: {
|
||||||
|
...pipeline,
|
||||||
|
config_controls,
|
||||||
|
properties,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneObjects(id: string, metadata: Metadata) {
|
||||||
|
this.logger.info('PipelinesClientService - FindOneObjects');
|
||||||
|
|
||||||
|
const { objects } = await lastValueFrom(
|
||||||
|
this.pipelineReadService.PipelineV2FindObjects({ id }, metadata),
|
||||||
|
);
|
||||||
|
this.logger.info('Done');
|
||||||
|
|
||||||
|
return { objects: JSON.parse(objects) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async initUploadFile(uploadFile, metadata) {
|
||||||
|
const body: Messages.PipelineV2UploadFileRequest = {
|
||||||
|
name: uploadFile.name,
|
||||||
|
parts: uploadFile.parts,
|
||||||
|
};
|
||||||
|
|
||||||
|
return lastValueFrom(
|
||||||
|
this.pipelineWriteService.PipelineV2UploadFile(body, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadLogs(pipeline_run_id: string, metadata: Metadata) {
|
||||||
|
this.logger.info('PipelinesClientService - DownloadLogs');
|
||||||
|
|
||||||
|
const { url } = await lastValueFrom(
|
||||||
|
this.pipelineReadService.PipelineV2DownloadLogs(
|
||||||
|
{ pipeline_run_id },
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.logger.info('Done');
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async completeUploadFile(uploadFile, metadata) {
|
||||||
|
const body: Messages.PipelineV2CompleteUploadFileRequest = {
|
||||||
|
upload_id: uploadFile.upload_id,
|
||||||
|
parts: JSON.stringify(uploadFile.parts),
|
||||||
|
name: uploadFile.file_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
return lastValueFrom(
|
||||||
|
this.pipelineWriteService.PipelineV2CompleteUploadFile(body, metadata),
|
||||||
|
).catch((err) => {
|
||||||
|
throw new HttpException(
|
||||||
|
err.details,
|
||||||
|
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMonitoringDashboardUrl(metadata: Metadata) {
|
||||||
|
this.logger.info('PipelinesClientService - getMonitoringDashboardUrl');
|
||||||
|
|
||||||
|
const res = await lastValueFrom(
|
||||||
|
this.pipelineReadService.PipelineV2GetDashboardUrl(
|
||||||
|
{
|
||||||
|
dashboard_id: '83',
|
||||||
|
exp: '15m',
|
||||||
|
metabase_customer_name: 'dadosferatech',
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.logger.info('Done');
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class INote {
|
||||||
|
@ApiProperty()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ProductboardController } from './productboard.controller';
|
||||||
|
|
||||||
|
describe('ProductboardController', () => {
|
||||||
|
let controller: ProductboardController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [ProductboardController],
|
||||||
|
providers: [],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<ProductboardController>(ProductboardController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user