mirror of
https://github.com/dadosfera/maestro.git
synced 2026-09-01 20:28:17 +00:00
Compare commits
768
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 | ||
|
|
3d39affc61 | ||
|
|
5d045b675c | ||
|
|
bb61990f6e | ||
|
|
80442c509a | ||
|
|
8551239038 | ||
|
|
4cf7c6e17b | ||
|
|
88c9c36eb0 | ||
|
|
4b317778f7 | ||
|
|
2eea9787cd | ||
|
|
c1ab5d152f | ||
|
|
8798f5d54e | ||
|
|
1da733b5bc | ||
|
|
930c8bc037 | ||
|
|
0913741500 | ||
|
|
3e9d0063ae | ||
|
|
2bc0fe1e9e | ||
|
|
f0fb6e6b4a | ||
|
|
74c27d9637 | ||
|
|
216c83d7fa | ||
|
|
08e66eeb1e | ||
|
|
cf3fd90787 | ||
|
|
bb1decb08f | ||
|
|
642cb0e70b | ||
|
|
0acf759e84 | ||
|
|
7bbb87a54a | ||
|
|
69af7ba626 | ||
|
|
0b14221d5a | ||
|
|
16bddf3d40 | ||
|
|
fb6ea7ba14 | ||
|
|
83d649a210 | ||
|
|
319fc80bfe | ||
|
|
35172681ce | ||
|
|
35db24dc59 | ||
|
|
ad9c1fc40c | ||
|
|
4660751023 | ||
|
|
25cf13923b | ||
|
|
d724c1644b | ||
|
|
c64529a37c | ||
|
|
8b06f0b12c | ||
|
|
311a4e4b93 | ||
|
|
6f8985baa7 | ||
|
|
11cb5a61da | ||
|
|
35c0520750 | ||
|
|
8c6ec8b1b9 | ||
|
|
77db6f3771 | ||
|
|
952a76e905 | ||
|
|
2ec62dae45 | ||
|
|
0d431a816b | ||
|
|
932e7413bd | ||
|
|
5cb83ee679 | ||
|
|
dcd558bd58 | ||
|
|
01e74473c0 | ||
|
|
d0c15fe290 | ||
|
|
b9d07e3677 | ||
|
|
2c8ed40bc6 | ||
|
|
46d9fd8446 | ||
|
|
1ca7e647d3 | ||
|
|
469f165cba | ||
|
|
203a161879 | ||
|
|
31e9a07489 | ||
|
|
e24925036d | ||
|
|
53f8f6986f | ||
|
|
5e0a422b35 | ||
|
|
8294b5591b | ||
|
|
01d41a4586 | ||
|
|
b8afdcf87d | ||
|
|
fa216053c2 | ||
|
|
e9a4a77eae | ||
|
|
fe041262eb | ||
|
|
7ef0ff1502 | ||
|
|
d8c55def7e | ||
|
|
dfcc3dcb53 | ||
|
|
080c8fc52a | ||
|
|
ecf0b1de51 | ||
|
|
2248dbe5a3 | ||
|
|
f5ec6661ff | ||
|
|
0deee0ef9c | ||
|
|
85f79f14cd | ||
|
|
01bdf134ca | ||
|
|
10a89632b5 | ||
|
|
24d8f832fa | ||
|
|
8efe3d26f8 | ||
|
|
6d21f29005 | ||
|
|
e526180902 | ||
|
|
be37eb0321 | ||
|
|
8c85172554 | ||
|
|
61296555fe | ||
|
|
5b6673a367 | ||
|
|
2968647e73 | ||
|
|
a1a55d645a | ||
|
|
b7748970cd | ||
|
|
8fb487435a | ||
|
|
e94ace09d0 | ||
|
|
c713b65979 | ||
|
|
195fdae0e6 | ||
|
|
0f079a7b22 | ||
|
|
93b44e8249 | ||
|
|
e18dc21670 | ||
|
|
ae443b5b03 | ||
|
|
3bfcc7a412 | ||
|
|
09e3b3fd33 | ||
|
|
6b7500136f | ||
|
|
7fa903fa1c | ||
|
|
19c35f5cc4 | ||
|
|
7b8b1bd8e0 | ||
|
|
5cc2d704ea | ||
|
|
d09bf96e6f | ||
|
|
0759f66144 | ||
|
|
8bd5bf1526 | ||
|
|
9772e18332 | ||
|
|
6f0f93d80d | ||
|
|
4b6d30859c | ||
|
|
5b4b1ae55e | ||
|
|
00dacfdbb9 | ||
|
|
7671567c8c | ||
|
|
64a8110649 | ||
|
|
4cbeea8281 | ||
|
|
adf9c11358 | ||
|
|
033752789a | ||
|
|
71617612b9 | ||
|
|
6219d61fe1 | ||
|
|
2fd51804fc | ||
|
|
874a3aa3a0 | ||
|
|
a9f68b87ca | ||
|
|
6807b3e1a9 | ||
|
|
5f17a7106f | ||
|
|
0a0ecb20bb | ||
|
|
35abb24ea7 | ||
|
|
372fc74bcb | ||
|
|
626f785994 | ||
|
|
f88f559b70 | ||
|
|
3981751ac0 | ||
|
|
61752a547f | ||
|
|
3356f306b3 | ||
|
|
6dd894e11b | ||
|
|
7a1b5c7dbf | ||
|
|
afc187d01a | ||
|
|
d0cd62cd3c | ||
|
|
acd4dafc57 | ||
|
|
d8ce82793d | ||
|
|
117704cd8c | ||
|
|
430e09c159 | ||
|
|
fa11df5684 | ||
|
|
e9b85537d7 | ||
|
|
17060332fd | ||
|
|
fde6d0a74c | ||
|
|
0ca6ed8636 | ||
|
|
7c5cf3f541 | ||
|
|
54f4331bcc | ||
|
|
2f006c607c | ||
|
|
9fbbfc2064 | ||
|
|
ba12d45bf7 | ||
|
|
ec418b00e7 | ||
|
|
89a02a4e8a | ||
|
|
cbee264182 | ||
|
|
2514245a44 | ||
|
|
0761c42041 | ||
|
|
688bf49f3b | ||
|
|
a1cdad42ab | ||
|
|
7546e6100d | ||
|
|
7cf178db42 | ||
|
|
2673e75f81 | ||
|
|
6382939e1e | ||
|
|
3bea2e4cda | ||
|
|
d9a7555d01 | ||
|
|
5122dbde85 | ||
|
|
4e6883a404 | ||
|
|
dcb3094890 | ||
|
|
c8e4e90100 | ||
|
|
09cff9fa15 | ||
|
|
79b624cea6 | ||
|
|
5356334258 | ||
|
|
5f6e4e88a5 | ||
|
|
32ab834834 | ||
|
|
ebbfe0abff | ||
|
|
4fb24d5b5c | ||
|
|
d76a2b4ab8 | ||
|
|
047737eadd | ||
|
|
00687dc84e | ||
|
|
61a1939c02 | ||
|
|
43cec9b6fa | ||
|
|
97214280bc | ||
|
|
e20ae71672 | ||
|
|
97b6047cf1 | ||
|
|
9c6d2b499a | ||
|
|
0de4a72388 | ||
|
|
f65f1d01ce | ||
|
|
6161865f1a | ||
|
|
d28684a9d0 | ||
|
|
f43c270c33 | ||
|
|
b0ccb39f2c | ||
|
|
54c2a41466 | ||
|
|
037f5cfec0 | ||
|
|
22631478b9 | ||
|
|
9f900882cb | ||
|
|
a24abed9ed | ||
|
|
b04a38bcc0 | ||
|
|
349ae780a7 | ||
|
|
60fb2ee7cb | ||
|
|
67706c5fc3 | ||
|
|
0fa4dcb933 | ||
|
|
04284e1feb | ||
|
|
3e7581e20c | ||
|
|
c61c37b268 | ||
|
|
5ce8099c25 | ||
|
|
9cdb0a5d38 | ||
|
|
57f47d3b2e | ||
|
|
4ab955a7a1 | ||
|
|
1ca3694b75 | ||
|
|
a6a35c507a | ||
|
|
cfcb8c1c42 | ||
|
|
a55da8e20f | ||
|
|
82310c6314 | ||
|
|
52f9d06ad0 | ||
|
|
a83f91a720 | ||
|
|
ccef7628f9 | ||
|
|
a5e424c854 | ||
|
|
339432df16 | ||
|
|
7dfa10d5ee | ||
|
|
c87ceec77b | ||
|
|
071c524a2e | ||
|
|
1de4eedd38 | ||
|
|
29276d6070 | ||
|
|
344a251de1 | ||
|
|
3b95032554 | ||
|
|
a0089b5d5d | ||
|
|
1b620b6fb0 | ||
|
|
1eb76d4b1c | ||
|
|
314bc5a562 | ||
|
|
d01b0357ed | ||
|
|
a206aad8db | ||
|
|
de57bfed1e | ||
|
|
14cfc46492 | ||
|
|
9d10e58b1b | ||
|
|
6dec2c1e56 | ||
|
|
23a65d18a6 | ||
|
|
d5cd562860 | ||
|
|
f5064420a7 | ||
|
|
bb5befb2f6 | ||
|
|
398360fadb | ||
|
|
13072978f5 | ||
|
|
b9612609d9 | ||
|
|
e7b37629a1 | ||
|
|
5b6994b247 | ||
|
|
039f837914 | ||
|
|
83dcc7942c | ||
|
|
5c079bb4f5 | ||
|
|
f383cf49d5 | ||
|
|
db78ee3d8b | ||
|
|
730fab8ed1 | ||
|
|
04e1e1ff2b | ||
|
|
d309c20cbf | ||
|
|
4cff593695 | ||
|
|
03d42df3fd | ||
|
|
ea04f59e04 | ||
|
|
d1961a9710 | ||
|
|
358dd6a15a | ||
|
|
cc3b34d87e | ||
|
|
88bc7fe625 | ||
|
|
137f129e13 | ||
|
|
f49c5ac1c1 | ||
|
|
d288a4f357 | ||
|
|
9f009309cb | ||
|
|
395db05b05 | ||
|
|
2d69e6db70 | ||
|
|
9514134db1 | ||
|
|
0c69cebca1 | ||
|
|
9791b8bdb0 | ||
|
|
6803ab3420 | ||
|
|
6aa7be98d2 | ||
|
|
f5665557c9 | ||
|
|
366987399e | ||
|
|
4356adea65 | ||
|
|
4c49dbc0ea | ||
|
|
59ffb24985 | ||
|
|
1cd191e970 | ||
|
|
8d23eaeea2 |
+2
-1
@@ -6,8 +6,9 @@ module.exports = {
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
|
||||
@@ -3,16 +3,21 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
description: "Deployment environment"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- stg
|
||||
- prd
|
||||
push_to_dockerhub:
|
||||
description: "Push image to Dockerhub?"
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
extract_environment:
|
||||
@@ -26,9 +31,11 @@ jobs:
|
||||
- name: Extract Environment
|
||||
run: |
|
||||
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
|
||||
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
|
||||
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') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- if: github.event_name != 'workflow_dispatch'
|
||||
name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v2
|
||||
uses: cycjimmy/semantic-release-action@v3
|
||||
id: semantic
|
||||
with:
|
||||
semantic_version: 16
|
||||
extra_plugins: |
|
||||
conventional-changelog-eslint
|
||||
conventional-changelog-eslint@4.0.0
|
||||
branches: |
|
||||
[
|
||||
'main'
|
||||
'main',
|
||||
{
|
||||
name: 'alpha',
|
||||
prerelease: true
|
||||
},
|
||||
{
|
||||
name: 'beta',
|
||||
prerelease: true
|
||||
}
|
||||
]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
deploy:
|
||||
build_ecr_image:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || needs.semantic_release.outputs.new_release_published == 'true' }}
|
||||
needs: [extract_environment, semantic_release]
|
||||
runs-on:
|
||||
[self-hosted, '${{ needs.extract_environment.outputs.environment }}']
|
||||
[self-hosted, "prd"]
|
||||
|
||||
steps:
|
||||
- name: Printing stats
|
||||
@@ -71,16 +85,12 @@ jobs:
|
||||
run: echo ${GITHUB_REF#refs/heads/}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update Pip
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
python3 -m pip install docker-compose --upgrade
|
||||
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
python3 -m pip install awscli --upgrade
|
||||
@@ -90,14 +100,14 @@ jobs:
|
||||
python3 -m pip install awsebcli --upgrade
|
||||
|
||||
- name: Configure AWS Region
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
id: aws
|
||||
with:
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Login to AWS 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
|
||||
env:
|
||||
@@ -105,32 +115,103 @@ jobs:
|
||||
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||
run: |
|
||||
docker-compose -f build.docker-compose.yml build
|
||||
docker-compose -f build.docker-compose.yml push
|
||||
docker compose -f build.docker-compose.yml build
|
||||
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: ${{ needs.extract_environment.outputs.environment }}
|
||||
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||
run: |
|
||||
sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
|
||||
sed -i -e "s/\${IMAGE_TAG}/$IMAGE_TAG/g" docker-compose.yml
|
||||
sed -i -e "s/\${ACCOUNT_ID}/$ACCOUNT_ID/g" docker-compose.yml
|
||||
zip deploy.zip docker-compose.yml -r .ebextensions
|
||||
docker compose -f build.docker-compose.dockerhub.yml build
|
||||
docker compose -f build.docker-compose.dockerhub.yml push
|
||||
|
||||
- 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: Create ZIP file to Deploy AWS Beanstalk
|
||||
# env:
|
||||
# ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||
# IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||
# ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
|
||||
# NODE_EXPORTER_URL: ${{needs.extract_environment.outputs.environment == 'prd' && '611330257153.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest' || '468720548566.dkr.ecr.us-east-1.amazonaws.com\/monitoring\/node_exporter:latest'}}
|
||||
# run: |
|
||||
# sed -i -e "s/\${ENV}/$ENV/g" docker-compose.yml
|
||||
# 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
|
||||
if: always()
|
||||
run: |
|
||||
docker system prune --volumes -a -f
|
||||
docker system df
|
||||
|
||||
helmfile-deploy:
|
||||
needs: [extract_environment, semantic_release, build_ecr_image]
|
||||
runs-on: [self-hosted, "prd-azure"]
|
||||
|
||||
steps:
|
||||
- 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 Apply
|
||||
env:
|
||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||
run: helmfile -f helmfiles/${ENV}.yaml sync --set image.tag=$IMAGE_TAG
|
||||
|
||||
@@ -6,35 +6,18 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: self-hosted
|
||||
runs-on: [self-hosted, prd]
|
||||
env:
|
||||
APP_NAME: ${{ github.event.repository.name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
python3 -m pip install docker-compose --upgrade
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
ENV: test
|
||||
IMAGE_TAG: test
|
||||
run: |
|
||||
export ENV=test
|
||||
export IMAGE_TAG=test
|
||||
export ACCOUNT_ID=test
|
||||
docker-compose -f build.docker-compose.yml build
|
||||
run: docker build -t ${APP_NAME}_teste --target test .
|
||||
|
||||
- name: Run Test
|
||||
env:
|
||||
ENV: test
|
||||
IMAGE_TAG: test
|
||||
ACCOUNT_ID: test
|
||||
APP_NAME: ${{ github.event.repository.name }}
|
||||
run: |
|
||||
export ENV=test
|
||||
export IMAGE_TAG=test
|
||||
docker-compose -f build.docker-compose.yml run --rm --entrypoint="npm run test" $APP_NAME
|
||||
run: docker run ${APP_NAME}_teste
|
||||
|
||||
- name: Remove Docker's Trash
|
||||
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
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug NestJs",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start:debug"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeVersion": "18.17",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
+40
-13
@@ -1,16 +1,43 @@
|
||||
FROM node:18.3.0-alpine3.15 as packages
|
||||
WORKDIR /packages
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN apk update \
|
||||
&& apk add --no-cache aws-cli \
|
||||
&& aws codeartifact login --tool npm --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \
|
||||
&& npm install
|
||||
FROM node:18.17-alpine AS base_image
|
||||
|
||||
FROM node:18.3.0-alpine3.15
|
||||
FROM base_image AS build_base
|
||||
WORKDIR /app
|
||||
COPY . /app/
|
||||
COPY --from=packages /packages/node_modules /app/node_modules
|
||||
RUN apk update
|
||||
# needed packages to build dependencies from source
|
||||
RUN apk add --no-cache aws-cli
|
||||
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
|
||||
FROM ci_image AS test
|
||||
ENV DUC_URL=0.0.0.0:50051
|
||||
ENTRYPOINT ["npm", "run", "test"]
|
||||
|
||||
|
||||
# dev build
|
||||
FROM build_base AS dev
|
||||
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||
aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1
|
||||
# flag --build-from-source is required to force-build sqlite3
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
ENTRYPOINT npm run start:dev
|
||||
|
||||
|
||||
FROM ci_image AS prod_build
|
||||
RUN npm run build
|
||||
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
|
||||
|
||||
Binary file not shown.
@@ -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,67 +2,102 @@
|
||||
<image src="./assets/maestro.svg" style="width:10rem">
|
||||
</p>
|
||||
|
||||
|
||||
# Maestro
|
||||
Maestro is the Dadosfera's gateway, it's responsible for the communication between the frontend application and Dadosfera's microservices.
|
||||
|
||||
## 🚀 Starting
|
||||
These instructions will allow you to get a working copy of the project on your local machine for development and testing purposes.
|
||||
Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços.
|
||||
|
||||
### 📋 Requirements
|
||||
- [NodeJS v18.3.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
|
||||
- Request access to AWS Console dev account for **all services** (avoid gradually asking for each needed service. it will slow down your development cycle)
|
||||
- Create your Access Key on the "Security credentials" menu
|
||||
- Set the Access Key on your local development machine
|
||||
- Request access to the dev, stg and prd VPNs
|
||||
```mermaid
|
||||
graph TD;
|
||||
Frontend<-->Maestro;
|
||||
Maestro<-->duc;
|
||||
Maestro<-->pi-factory;
|
||||
Maestro<-->in-factory;
|
||||
```
|
||||
|
||||
### 🔧 Installation<a id="installation"></a>
|
||||
- Clone the repository
|
||||
- SSH
|
||||
```
|
||||
git clone git@github.com:dadosfera/maestro.git
|
||||
```
|
||||
or
|
||||
É uma API REST, desenvolvida em NodeJs utilizando o Framework [NestJs](https://docs.nestjs.com/).
|
||||
|
||||
- HTTPS
|
||||
```
|
||||
git clone https://github.com/dadosfera/maestro.git
|
||||
```
|
||||
## 🚀 Iniciando
|
||||
|
||||
- Select the correct node version (optional, only if using [NVM](https://github.com/nvm-sh/nvm)):
|
||||
```sh
|
||||
nvm use
|
||||
```
|
||||
Estas instruções permitirão que você obtenha uma cópia funcional do projeto em sua máquina local para desenvolvimento e testes.
|
||||
|
||||
- Install the project dependencies:
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
### 📋 Requisitos
|
||||
|
||||
- Setup the following enviroment variables:
|
||||
```
|
||||
ENV=
|
||||
DUC_URL=
|
||||
INFACTORY_URL=
|
||||
OTFACTORY_URL=
|
||||
PIFACTORY_URL=
|
||||
SM_OAUTH_PATH=
|
||||
```
|
||||
- [NodeJS v18.17 / NPM v9.6.7](https://nodejs.org/pt-br/download/)
|
||||
|
||||
- Start the server:
|
||||
```sh
|
||||
# dev mode
|
||||
npm run start:dev
|
||||
> Dica: Utilize [NVM](https://github.com/nvm-sh/nvm) para gerenciar facilmente as versões do node
|
||||
|
||||
# or in debug mode
|
||||
npm run start:debug
|
||||
```
|
||||
The service should start successfully.
|
||||
- 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
|
||||
|
||||
## Authentication decorators
|
||||
Maestro have utilities to ease the user authentication on every controller and route. The following decorators are available:
|
||||
### 🔧 Instalação<a id="installation"></a>
|
||||
|
||||
- Clone o repositório
|
||||
|
||||
```sh
|
||||
git clone git@github.com:dadosfera/maestro.git
|
||||
```
|
||||
|
||||
- Selecione a versão correta do node (opcional, apenas se estiver usando o [NVM](https://github.com/nvm-sh/nvm)):
|
||||
|
||||
```sh
|
||||
nvm use
|
||||
```
|
||||
|
||||
- Instale as dependências do projeto:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
- Configure as seguintes variáveis de ambiente:
|
||||
|
||||
```
|
||||
ENV=
|
||||
DUC_URL=
|
||||
INFACTORY_URL=
|
||||
OTFACTORY_URL=
|
||||
PIFACTORY_URL=
|
||||
SM_OAUTH_PATH=
|
||||
```
|
||||
|
||||
- Inicie o servidor:
|
||||
|
||||
```sh
|
||||
npm run start:dev
|
||||
```
|
||||
|
||||
> Se preferir, utilize o debbuger do VSCode apertando F5
|
||||
|
||||
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`
|
||||
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
|
||||
import { Authenticated } from '../../authentication/authentication.decorator';
|
||||
@@ -74,7 +109,7 @@ class FooController {
|
||||
|
||||
@Get('bar')
|
||||
async getBar() {
|
||||
this.logger.info('user is authenticated!');
|
||||
this.logger.info('usuário está autenticado!');
|
||||
|
||||
return { authenticated: true };
|
||||
}
|
||||
@@ -91,14 +126,14 @@ class FooController {
|
||||
@Get('bar')
|
||||
@Authenticated()
|
||||
async getBar() {
|
||||
this.logger.info('user is authenticated!');
|
||||
this.logger.info('usuário está autenticado!');
|
||||
|
||||
return { authenticated: true };
|
||||
}
|
||||
|
||||
@Post('bar')
|
||||
async postBar() {
|
||||
this.logger.info('user is NOT authenticated!');
|
||||
this.logger.info('usuário NÃO está autenticado!');
|
||||
|
||||
return { authenticated: false };
|
||||
}
|
||||
@@ -106,11 +141,12 @@ class FooController {
|
||||
```
|
||||
|
||||
### `@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
|
||||
import { RequireAllPermissions } from '../../authentication/authentication.decorator';
|
||||
import { Permissions } from '../../authentication/permissions.enum';
|
||||
import { PERMISSIONS } from '../../authentication/permissions.enum';
|
||||
|
||||
@Controller('foo')
|
||||
class FooController {
|
||||
@@ -125,11 +161,12 @@ class FooController {
|
||||
```
|
||||
|
||||
### `@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
|
||||
import { RequireSomePermission } from '../../authentication/authentication.decorator';
|
||||
import { Permissions } from '../../authentication/permissions.enum';
|
||||
import { PERMISSIONS } from '../../authentication/permissions.enum';
|
||||
|
||||
@Controller('foo')
|
||||
@RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.CREATE)
|
||||
@@ -144,17 +181,19 @@ class FooController {
|
||||
```
|
||||
|
||||
### `@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:
|
||||
- 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`
|
||||
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.
|
||||
|
||||
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
|
||||
import { AuthenticateCondition } from '../../authentication/authentication.decorator';
|
||||
|
||||
@Controller('foo')
|
||||
// allow requests only from localhost
|
||||
// permitir solicitações apenas do localhost
|
||||
@AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1')
|
||||
class FooController {
|
||||
/* ... */
|
||||
@@ -165,61 +204,68 @@ class FooController {
|
||||
}
|
||||
|
||||
@Post('bar')
|
||||
// allow requests only from a specific customer
|
||||
@AuthenticateCondition((request: Request, user: RequestUser) => user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef')
|
||||
// permitir solicitações apenas de um cliente específico
|
||||
@AuthenticateCondition(
|
||||
(request: Request, user: RequestUser) =>
|
||||
user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef',
|
||||
)
|
||||
async postBar() {
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Note on authentication decorators
|
||||
- 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.
|
||||
### Nota sobre decoradores de autenticação
|
||||
|
||||
- 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.
|
||||
|
||||
```ts
|
||||
import {
|
||||
RequireAllPermissions,
|
||||
RequireSomePermission,
|
||||
AuthenticateCondition,
|
||||
} from '../../authentication/authentication.decorator';
|
||||
import { Permissions } from '../../authentication/permissions.enum';
|
||||
import { Waa, Baz } from './authenticationFunctions'
|
||||
- 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`.
|
||||
|
||||
@Controller('foo')
|
||||
@RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST)
|
||||
@RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.ADMIN)
|
||||
@AuthenticateCondition(Waa)
|
||||
@AuthenticateCondition(Baz)
|
||||
class FooController {
|
||||
```ts
|
||||
import {
|
||||
RequireAllPermissions,
|
||||
RequireSomePermission,
|
||||
AuthenticateCondition,
|
||||
} from '../../authentication/authentication.decorator';
|
||||
import { PERMISSIONS } from '../../authentication/permissions.enum';
|
||||
import { Waa, Baz } from './authenticationFunctions';
|
||||
|
||||
@Controller('foo')
|
||||
@RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST)
|
||||
@RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.ADMIN)
|
||||
@AuthenticateCondition(Waa)
|
||||
@AuthenticateCondition(Baz)
|
||||
class FooController {
|
||||
/* ... */
|
||||
|
||||
@Get('bar')
|
||||
async getBar() {
|
||||
/* ... */
|
||||
|
||||
@Get('bar')
|
||||
async getBar() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Post('bar')
|
||||
@RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.USE, Permissions.BAR.REQUEST)
|
||||
async postBar() {
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- If `@RequireAllPermissions` and `@RequireSomePermission` are used with **only a single permission**, they present the **exactly same behavior**.
|
||||
@Post('bar')
|
||||
@RequireAllPermissions(
|
||||
Permissions.BAR.MANAGE,
|
||||
Permissions.BAR.USE,
|
||||
Permissions.BAR.REQUEST,
|
||||
)
|
||||
async postBar() {
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// same behavior
|
||||
@RequireAllPermissions(Permissions.BAR.MANAGE)
|
||||
@RequireSomePermission(Permissions.BAR.MANAGE)
|
||||
```
|
||||
- Se `@RequireAllPermissions` e `@RequireSomePermission` forem usados com **apenas uma única permissão**, eles apresentam o **exatamente mesmo comportamento**.
|
||||
|
||||
```ts
|
||||
// mesmo comportamento
|
||||
@RequireAllPermissions(Permissions.BAR.MANAGE)
|
||||
@RequireSomePermission(Permissions.BAR.MANAGE)
|
||||
```
|
||||
|
||||
## Decorador de parâmetro `@User`
|
||||
|
||||
## `@User` parameter decorator
|
||||
The requesting user data can be obtained using the @User parameter decorator, like in the following snippet:
|
||||
Os dados do usuário solicitante podem ser obtidos usando o decorador de parâmetro @User, como no exemplo a seguir:
|
||||
|
||||
```ts
|
||||
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
|
||||
import { User, RequestUser } from '../../authentication/user.decorator';
|
||||
@@ -263,39 +309,46 @@ class UserController {
|
||||
}
|
||||
```
|
||||
|
||||
## 📦 Development
|
||||
### ⌨️ Coding Style
|
||||
By default, we use [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) with default settings.
|
||||
## 📦 Desenvolvimento
|
||||
|
||||
**We recommend using Visual Studio Code and installing the recommended extensions to ease the development process.**
|
||||
### ⌨️ Estilo de Codificação
|
||||
|
||||
### Commits pattern
|
||||
Our workflow pipeline follows the conventional commits specs ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) to release versions accordingly.
|
||||
Por padrão, usamos [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) com configurações padrão.
|
||||
|
||||
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
|
||||
- **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`.
|
||||
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.
|
||||
|
||||
**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
|
||||
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
|
||||
**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.**
|
||||
|
||||
## 🛠️ Built with
|
||||
Some technologies used in this project:
|
||||
- **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`.
|
||||
|
||||
- [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
|
||||
The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).
|
||||
- **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`.
|
||||
|
||||
### 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:
|
||||
maestro:
|
||||
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:
|
||||
maestro:
|
||||
image: ${ACCOUNT_ID}.dkr.ecr.us-east-1.amazonaws.com/microservices/maestro_${ENV}:${IMAGE_TAG}
|
||||
@@ -10,7 +9,7 @@ services:
|
||||
- .env
|
||||
|
||||
node_exporter:
|
||||
image: prom/node-exporter:v1.3.1
|
||||
image: ${NODE_EXPORTER_URL}
|
||||
container_name: node_exporter
|
||||
restart: always
|
||||
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,18 @@
|
||||
import type { Config } from '@jest/types';
|
||||
const config: Config.InitialOptions = {
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
modulePaths: ['<rootDir>'],
|
||||
roots: ['<rootDir>/src/', '<rootDir>/test/'],
|
||||
testRegex: '.*\\.(test|spec)\\.[jt]s$',
|
||||
transform: { '\\.[jt]s$': 'ts-jest' },
|
||||
collectCoverageFrom: ['**/*.[jt]s'],
|
||||
coverageDirectory: 'coverage',
|
||||
coveragePathIgnorePatterns: [
|
||||
'<rootDir>/node_modules/',
|
||||
'.*\\.module\\.[jt]s$',
|
||||
],
|
||||
// moduleDirectories: ['node_modules'], // default is already 'node_modules'
|
||||
// rootDir: '.', //No need
|
||||
// testEnvironment: 'node', //Defaults to 'node'
|
||||
};
|
||||
export default config;
|
||||
@@ -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
|
||||
+5
-1
@@ -3,7 +3,11 @@
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"assets": [
|
||||
"**/*.proto"
|
||||
"**/*.proto",
|
||||
{
|
||||
"include": "i18n/**/*",
|
||||
"watchAssets": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@nestjs/swagger"
|
||||
|
||||
Generated
+4363
-11457
File diff suppressed because it is too large
Load Diff
+52
-63
@@ -6,105 +6,94 @@
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"engines": {
|
||||
"node": "18.3.0"
|
||||
"node": "18.17"
|
||||
},
|
||||
"scripts": {
|
||||
"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",
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:dev": "export INTERNAL_SWAGGER=true && nest start --watch",
|
||||
"start:debug": "npm run start:dev",
|
||||
"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",
|
||||
"test": "jest",
|
||||
"test": "jest --detectOpenHandles --forceExit",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.112.0",
|
||||
"@grpc/grpc-js": "^1.6.7",
|
||||
"@grpc/proto-loader": "^0.6.13",
|
||||
"@nestjs/common": "^8.4.7",
|
||||
"@nestjs/config": "^1.2.1",
|
||||
"@nestjs/core": "^8.4.7",
|
||||
"@nestjs/mapped-types": "*",
|
||||
"@nestjs/microservices": "^8.4.7",
|
||||
"@nestjs/passport": "^8.2.2",
|
||||
"@nestjs/platform-express": "^8.4.7",
|
||||
"@nestjs/schedule": "^1.1.0",
|
||||
"@nestjs/swagger": "^5.2.1",
|
||||
"axios": "^0.25.0",
|
||||
"cron-parser": "^4.4.0",
|
||||
"dadosfera-logs": "^1.0.0-alpha.10",
|
||||
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
||||
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
||||
"@dadosfera/protospack": "2.5.3",
|
||||
"@dadosfera/protospack-v2": "3.37.0-beta.3",
|
||||
"@grpc/grpc-js": "^1.9.3",
|
||||
"@grpc/proto-loader": "^0.7.9",
|
||||
"@nestjs/cli": "^9.5.0",
|
||||
"@nestjs/common": "^9.4.3",
|
||||
"@nestjs/config": "^2.3.4",
|
||||
"@nestjs/core": "^9.4.3",
|
||||
"@nestjs/mapped-types": "^1.2.2",
|
||||
"@nestjs/microservices": "^9.4.3",
|
||||
"@nestjs/passport": "^9.0.3",
|
||||
"@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",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"cron-parser": "^4.9.0",
|
||||
"dotenv": "^14.3.2",
|
||||
"elastic-apm-node": "^3.36.0",
|
||||
"helmet": "^5.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"elastic-apm-node": "^3.50.0",
|
||||
"helmet": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.5",
|
||||
"mixpanel": "^0.17.0",
|
||||
"ms": "^3.0.0-canary.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-forcedotcom": "^0.1.5",
|
||||
"passport-forcedotcom": "^0.2.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-hubspot-oauth2": "^1.0.3",
|
||||
"passport-mailchimp": "^1.1.0",
|
||||
"protospack": "2.5.1",
|
||||
"protospack-v2": "1.1.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.5.5",
|
||||
"swagger-ui-express": "^4.4.0"
|
||||
"swagger-ui-express": "^4.6.3"
|
||||
},
|
||||
"overrides": {
|
||||
"multer": "1.4.5-lts.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.2.8",
|
||||
"@nestjs/schematics": "^8.0.11",
|
||||
"@nestjs/testing": "^8.4.7",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/jsonwebtoken": "^8.5.9",
|
||||
"@types/jwk-to-pem": "^2.0.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^16.11.41",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/node": "^16.18.52",
|
||||
"@types/passport-facebook": "^2.1.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",
|
||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jest": "^27.5.1",
|
||||
"nock": "^13.2.7",
|
||||
"prettier": "^2.7.1",
|
||||
"nock": "^13.3.3",
|
||||
"prettier": "^2.8.8",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.2.3",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"tsconfig-paths": "^3.14.1",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
"ts-loader": "^9.4.4",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^3.14.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
+47
-103
@@ -1,88 +1,37 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
|
||||
import { InputsController } from './modules/inputs/inputs.controller';
|
||||
import { TransformationsController } from './modules/transformations/transformations.controller';
|
||||
import { OutputsController } from './modules/outputs/outputs.controllers';
|
||||
import { PipelinesController } from './modules/pipelines/pipelines.controller';
|
||||
import { AuthController } from './modules/auth/auth.controller';
|
||||
import { HealthController } from './modules/health/health.controller';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { InputsService } from './modules/inputs/inputs.service';
|
||||
import { TransformationsService } from './modules/transformations/transformations.service';
|
||||
import { OutputsService } from './modules/outputs/outputs.service';
|
||||
import { PipelinesService } from './modules/pipelines/pipelines.service';
|
||||
import { HealthService } from './modules/health/health.service';
|
||||
|
||||
import { AuthClientService } from './clients/auth/client.service';
|
||||
import { InputsClientService } from './clients/inputs/client.service';
|
||||
import { TransformationsClientService } from './clients/transformations/client.service';
|
||||
import { OutputsClientService } from './clients/outputs/client.service';
|
||||
import { PipelinesClientService } from './clients/pipelines/client.service';
|
||||
import { PermissionsClientService } from './clients/permissions/client.service';
|
||||
|
||||
import { OutputsClientConfiguration } from './clients/outputs/client.config';
|
||||
import { TransformationsClientConfiguration } from './clients/transformations/client.config';
|
||||
import { DucClient } from './clients/duc/client.config';
|
||||
import { InputsClientConfiguration } from './clients/inputs/client.config';
|
||||
import { PipelinesClientConfiguration } from './clients/pipelines/client.config';
|
||||
import { CatalogController } from './modules/catalog/catalog.controller';
|
||||
import { CatalogService } from './modules/catalog/catalog.service';
|
||||
import { OauthController } from './modules/oauth/oauth.controller';
|
||||
import { getOauthSecrets } from './utils/OauthSecrets';
|
||||
import { HubspotStrategy } from './modules/oauth/passport-strategies/hubspot-strategy';
|
||||
import { FacebookStrategy } from './modules/oauth/passport-strategies/facebook-strategy';
|
||||
import { AuthenticationGuard } from './authentication/authentication.guard';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { GoogleStrategy } from './modules/oauth/passport-strategies/google-strategy';
|
||||
import { MailChimpStrategy } from './modules/oauth/passport-strategies/mailchimp-strategy';
|
||||
import { SalesforceStrategy } from './modules/oauth/passport-strategies/salesforce-strategy';
|
||||
import { ConnectorClientConfiguration } from './clients/connector/client.config';
|
||||
import { ConnectorController } from './modules/connector/connector.controller';
|
||||
import { ConnectorClientService } from './clients/connector/client.service';
|
||||
|
||||
const ducClient = new DucClient();
|
||||
const inputClient = new InputsClientConfiguration();
|
||||
const outputClient = new OutputsClientConfiguration();
|
||||
const pipelineClient = new PipelinesClientConfiguration();
|
||||
const transformationClient = new TransformationsClientConfiguration();
|
||||
const connectorClient = new ConnectorClientConfiguration();
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
|
||||
import { UsersModule } from './modules/users/users.module';
|
||||
import { RolesModule } from './modules/roles/roles.module';
|
||||
import { ConnectorModule } from './modules/connector/connector.module';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { PermissionsModule } from './modules/permissions/permissions.module';
|
||||
import { TermsOfUseModule } from './modules/termsOfUse/termsOfUse.module';
|
||||
import { ConnectionModule } from './modules/connection/connection.module';
|
||||
import { ConnectionTestModule } from './modules/connection-test/connection-test.module';
|
||||
import { NetworkConfigModule } from './modules/network-config/network-config.module';
|
||||
import { InputsModule } from './modules/inputs/inputs.module';
|
||||
import { OauthModule } from './modules/oauth/oauth.module';
|
||||
import { PipelinesModule } from './modules/pipelines/pipelines.module';
|
||||
import { TransformationsModule } from './modules/transformations/transformations.module';
|
||||
import { HealthModule } from './modules/health/health.module';
|
||||
import { CatalogModule } from './modules/catalog/catalog.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
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({
|
||||
controllers: [
|
||||
InputsController,
|
||||
TransformationsController,
|
||||
OutputsController,
|
||||
PipelinesController,
|
||||
AuthController,
|
||||
HealthController,
|
||||
CatalogController,
|
||||
OauthController,
|
||||
ConnectorController,
|
||||
],
|
||||
providers: [
|
||||
DadosferaLogger,
|
||||
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
|
||||
InputsService,
|
||||
TransformationsService,
|
||||
OutputsService,
|
||||
PipelinesService,
|
||||
ConnectorClientService,
|
||||
HealthService,
|
||||
InputsClientService,
|
||||
TransformationsClientService,
|
||||
OutputsClientService,
|
||||
PipelinesClientService,
|
||||
AuthClientService,
|
||||
PermissionsClientService,
|
||||
CatalogService,
|
||||
HubspotStrategy,
|
||||
FacebookStrategy,
|
||||
GoogleStrategy,
|
||||
MailChimpStrategy,
|
||||
SalesforceStrategy,
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthenticationGuard,
|
||||
@@ -92,33 +41,28 @@ const connectorClient = new ConnectorClientConfiguration();
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
|
||||
ClientsModule.register([
|
||||
{
|
||||
name: 'INPUTS_PACKAGE',
|
||||
...inputClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'TRANSFORMATIONS_PACKAGE',
|
||||
...transformationClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'OUTPUTS_PACKAGE',
|
||||
...outputClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'DUC_PACKAGE',
|
||||
...ducClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'PIPELINES_PACKAGE',
|
||||
...pipelineClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'CONNECTOR_PACKAGE',
|
||||
...connectorClient.config(),
|
||||
},
|
||||
]),
|
||||
AuthModule,
|
||||
ConnectionModule,
|
||||
NetworkConfigModule,
|
||||
InputsModule,
|
||||
PipelinesV2Module,
|
||||
CatalogModule,
|
||||
ConnectorModule,
|
||||
PermissionsModule,
|
||||
TermsOfUseModule,
|
||||
ConnectionTestModule,
|
||||
PipelinesModule,
|
||||
TransformationsModule,
|
||||
UsersModule,
|
||||
RolesModule,
|
||||
OauthModule,
|
||||
ProductboardModule,
|
||||
MixpanelModule,
|
||||
CustomersModule,
|
||||
OpenDataModule,
|
||||
ThemeModule,
|
||||
//Always leave HealthModule last, so it is on the bottom of swagger
|
||||
HealthModule
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Request } from 'express';
|
||||
import { CustomDecorator } from '@nestjs/common';
|
||||
import { Permission } 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: Permission[]) {
|
||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
|
||||
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
|
||||
);
|
||||
}
|
||||
|
||||
export function RequireSomePermission(...permissions: Permission[]) {
|
||||
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);
|
||||
}
|
||||
@@ -5,19 +5,23 @@ import { APP_GUARD } from '@nestjs/core';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { Body, Controller, Get } from '@nestjs/common';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import {
|
||||
AuthenticateCondition,
|
||||
Authenticated,
|
||||
RequireAllPermissions,
|
||||
RequireSomePermission,
|
||||
} from './authentication.decorator';
|
||||
} from '../decorators/authentication.decorator';
|
||||
import { AuthenticationGuard } from './authentication.guard';
|
||||
import { Permissions } from './permissions.enum';
|
||||
import { AuthClientService } from '../clients/auth/client.service';
|
||||
import { PERMISSIONS_GROUPS } from './permissions.enum';
|
||||
import { AuthClientService } from '../modules/auth/auth.service';
|
||||
|
||||
import ErrorCodes from '../utils/errorCodes';
|
||||
|
||||
const logger = {
|
||||
info: (...args) => args,
|
||||
error: (...args) => args,
|
||||
};
|
||||
@Controller('no-class-auth')
|
||||
class NoClassAuthController {
|
||||
@Get('body')
|
||||
@@ -32,19 +36,25 @@ class NoClassAuthController {
|
||||
}
|
||||
|
||||
@Get('required-permission')
|
||||
@RequireSomePermission(Permissions.ZENDESK.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
|
||||
async requiredPermission(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
|
||||
@Get('has-all-permissions')
|
||||
@RequireAllPermissions(Permissions.ZENDESK.OPEN, Permissions.METABASE.OPEN)
|
||||
@RequireAllPermissions(
|
||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
)
|
||||
async hasAllPermissions(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
|
||||
@Get('has-some-permission')
|
||||
@RequireSomePermission(Permissions.ZENDESK.OPEN, Permissions.METABASE.OPEN)
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
)
|
||||
async hasSomePermission(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
@@ -52,7 +62,7 @@ class NoClassAuthController {
|
||||
|
||||
@Controller('class-auth-condition')
|
||||
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
|
||||
@RequireSomePermission(Permissions.METABASE.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE)
|
||||
class ClassAuthConditionController {
|
||||
@Get('body')
|
||||
async getBody(@Body() body) {
|
||||
@@ -67,7 +77,7 @@ class ClassAuthConditionController {
|
||||
|
||||
@Get('required-permission')
|
||||
@AuthenticateCondition((req) => req.get('x-on-route') === 'ok')
|
||||
@RequireSomePermission(Permissions.ZENDESK.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
|
||||
async requiredPermission(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
@@ -104,7 +114,7 @@ describe('authentication.guard', () => {
|
||||
},
|
||||
{
|
||||
provide: DadosferaLogger,
|
||||
useValue: { logger: console },
|
||||
useValue: { logger },
|
||||
},
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
@@ -430,17 +440,17 @@ describe('authentication.guard', () => {
|
||||
NoClassAuthTest(null, null);
|
||||
ClassAuthConditionTest(null, null);
|
||||
|
||||
const tokenZ = CreateToken([Permissions.ZENDESK.OPEN]);
|
||||
const tokenZ = CreateToken([PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN]);
|
||||
NoClassAuthTest(tokenZ, ['zendesk']);
|
||||
ClassAuthConditionTest(tokenZ, ['zendesk']);
|
||||
|
||||
const tokenM = CreateToken([Permissions.METABASE.OPEN]);
|
||||
const tokenM = CreateToken([PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE]);
|
||||
NoClassAuthTest(tokenM, ['metabase']);
|
||||
ClassAuthConditionTest(tokenM, ['metabase']);
|
||||
|
||||
const tokenZM = CreateToken([
|
||||
Permissions.ZENDESK.OPEN,
|
||||
Permissions.METABASE.OPEN,
|
||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
]);
|
||||
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
|
||||
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
|
||||
|
||||
@@ -3,19 +3,18 @@ import {
|
||||
CanActivate,
|
||||
OnApplicationBootstrap,
|
||||
ExecutionContext,
|
||||
Logger,
|
||||
Inject,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import assert from 'assert';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { AuthClientService } from '../clients/auth/client.service';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { AuthClientService } from '../modules/auth/auth.service';
|
||||
import {
|
||||
AuthenticationFunction,
|
||||
AUTH_FUNCTION_KEY,
|
||||
} from './authentication.decorator';
|
||||
import { RequestUser } from './user.decorator';
|
||||
} from '../decorators/authentication.decorator';
|
||||
import { RequestUser } from '../decorators/user.decorator';
|
||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||
import ErrorCodes from '../utils/errorCodes';
|
||||
|
||||
@@ -23,7 +22,7 @@ import ErrorCodes from '../utils/errorCodes';
|
||||
export class AuthenticationGuard
|
||||
implements CanActivate, OnApplicationBootstrap
|
||||
{
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
|
||||
pems: Map<string, string>;
|
||||
|
||||
@@ -96,7 +95,7 @@ export class AuthenticationGuard
|
||||
);*/
|
||||
|
||||
assert(
|
||||
accessTokenDecoded.payload.token_use === 'access',
|
||||
accessTokenDecoded?.payload?.token_use === 'access',
|
||||
'should be an access token',
|
||||
);
|
||||
|
||||
@@ -108,7 +107,7 @@ export class AuthenticationGuard
|
||||
} catch (err) {
|
||||
// log errors if authentication is required
|
||||
if (mustBeAuthenticated) {
|
||||
this.logger.error('Unable to verify duc access token\n' + err.stack);
|
||||
this.logger.error('Unable to verify duc access token\n' + err?.stack);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -122,6 +121,7 @@ export class AuthenticationGuard
|
||||
customer_id: accessTokenPayload.customer_id,
|
||||
customer_name: accessTokenPayload.customer_name,
|
||||
customer_tier: accessTokenPayload.customer_tier,
|
||||
access_token: accessToken,
|
||||
};
|
||||
// TODO: for backwards compatibility. remove in the future
|
||||
request.body.info = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import assert from 'assert';
|
||||
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
@@ -8,10 +9,24 @@ export enum PermissionUsages {
|
||||
INTERNAL = 'internal',
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
export interface PermissionsObjectPermission {
|
||||
seqid: number;
|
||||
claim: string;
|
||||
usage: PermissionUsages;
|
||||
name?: Record<LanguageEnum, string>;
|
||||
}
|
||||
export interface PermissionsObjectPermissionTranslated {
|
||||
seqid: number;
|
||||
claim: string;
|
||||
usage: PermissionUsages;
|
||||
name?: string;
|
||||
}
|
||||
export interface PermissionsObjectGroup {
|
||||
title: {
|
||||
'pt-br': string;
|
||||
'en-us': string;
|
||||
};
|
||||
permissions: Record<string, PermissionsObjectPermission>;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -22,216 +37,611 @@ export interface Permission {
|
||||
/*export const Permissions: {
|
||||
[P in keyof any]: { [Q in keyof any]: Permission };
|
||||
} = {*/
|
||||
export const Permissions = {
|
||||
export const PERMISSIONS_GROUPS = {
|
||||
AUTH: {
|
||||
CREATE: {
|
||||
seqid: 18,
|
||||
claim: 'POST /auth',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Autenticação',
|
||||
'en-us': 'Authentication',
|
||||
'es-es': 'Authentication',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 18,
|
||||
claim: 'POST /auth',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': '',
|
||||
'en-us': '',
|
||||
'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: {
|
||||
CREATE: {
|
||||
seqid: 23,
|
||||
claim: 'POST /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Coletar | Pipelines',
|
||||
'en-us': 'Collect | Pipelines',
|
||||
'es-es': 'Colecta | Pipelines',
|
||||
},
|
||||
GET: {
|
||||
seqid: 13,
|
||||
claim: 'GET /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 29,
|
||||
claim: 'PUT /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 5,
|
||||
claim: 'DELETE /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 23,
|
||||
claim: 'POST /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Criar pipelines',
|
||||
'en-us': 'Create pipelines',
|
||||
'es-es': 'Crear pipelines',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
seqid: 13,
|
||||
claim: 'GET /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Buscar pipelines',
|
||||
'en-us': 'Get pipelines',
|
||||
'es-es': 'Obtener pipelines',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 29,
|
||||
claim: 'PUT /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Atualizar pipeline',
|
||||
'en-us': 'Update pipeline',
|
||||
'es-es': 'Actualizar pipeline',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 5,
|
||||
claim: 'DELETE /pipelines',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Excluir pipeline',
|
||||
'en-us': 'Delete pipeline',
|
||||
'es-es': 'Eliminar pipeline',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
INPUT: {
|
||||
CREATE: {
|
||||
seqid: 21,
|
||||
claim: 'POST /inputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
CONNECTION: {
|
||||
title: {
|
||||
'pt-br': 'Coletar | Fontes de dados',
|
||||
'en-us': 'Collect | Sources',
|
||||
'es-es': 'Colecta | Sources',
|
||||
},
|
||||
GET: {
|
||||
seqid: 9,
|
||||
claim: 'GET /inputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 37,
|
||||
claim: 'connection:create',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Cadastrar Fonte',
|
||||
'en-us': 'Create Source',
|
||||
'es-es': 'Crear Source',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 38,
|
||||
claim: 'connection:delete',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Excluir Fonte',
|
||||
'en-us': 'Remove Source',
|
||||
'es-es': 'Eliminar Source',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 39,
|
||||
claim: 'connection:update',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Editar Fonte',
|
||||
'en-us': 'Edit Source',
|
||||
'es-es': 'Editar Source',
|
||||
},
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 27,
|
||||
claim: 'PUT /inputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
NETWORK_CONFIG: {
|
||||
title: {
|
||||
'pt-br': 'Coletar | Redes',
|
||||
'en-us': 'Collect | Network',
|
||||
'es-es': 'Colecta | Network',
|
||||
},
|
||||
UPDATE_PARTIAL: {
|
||||
seqid: 17,
|
||||
claim: 'PATCH /inputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 3,
|
||||
claim: 'DELETE /inputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
GET_ENTITIES: {
|
||||
seqid: 10,
|
||||
claim: 'GET /inputs/available-entities',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 35,
|
||||
claim: 'network_config:create',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Cadastrar VPN',
|
||||
'en-us': 'Create VPN',
|
||||
'es-es': 'Crear VPN',
|
||||
},
|
||||
},
|
||||
// DELETE: {
|
||||
// seqid: 36,
|
||||
// claim: 'network_config:delete',
|
||||
// usage: PermissionUsages.PUBLIC,
|
||||
// name: {
|
||||
// 'pt-br': 'Excluir VPN',
|
||||
// 'en-us': 'Remove VPN',
|
||||
// 'es-es': 'Eliminar VPN',
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
|
||||
OUTPUT: {
|
||||
CREATE: {
|
||||
seqid: 22,
|
||||
claim: 'POST /outputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Output',
|
||||
'en-us': 'Output',
|
||||
'es-es': 'Output',
|
||||
},
|
||||
GET: {
|
||||
seqid: 12,
|
||||
claim: 'GET /outputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 28,
|
||||
claim: 'PUT /outputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 4,
|
||||
claim: 'DELETE /outputs',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 22,
|
||||
claim: 'POST /outputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Criar outputs',
|
||||
'en-us': 'Create outputs',
|
||||
'es-es': 'Crear outputs',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
seqid: 12,
|
||||
claim: 'GET /outputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Buscar outputs',
|
||||
'en-us': 'Get outputs',
|
||||
'es-es': 'Obtener outputs',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 28,
|
||||
claim: 'PUT /outputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Editar outputs',
|
||||
'en-us': 'Edit outputs',
|
||||
'es-es': 'Editar outputs',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 4,
|
||||
claim: 'DELETE /outputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Excluir outputs',
|
||||
'en-us': 'Remove outputs',
|
||||
'es-es': 'Eliminar outputs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
TRANSFORMATIONS: {
|
||||
CREATE: {
|
||||
seqid: 24,
|
||||
claim: 'POST /transformations',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Micro-transformações',
|
||||
'en-us': 'Micro-transformations',
|
||||
'es-es': 'Micro-transformaciones',
|
||||
},
|
||||
GET: {
|
||||
seqid: 15,
|
||||
claim: 'GET /transformations',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 30,
|
||||
claim: 'PUT /transformations',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 6,
|
||||
claim: 'DELETE /transformations',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 24,
|
||||
claim: 'POST /transformations',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Criar micro-transformações',
|
||||
'en-us': 'Create micro-transformations',
|
||||
'es-es': 'Crear micro-transformaciones',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
seqid: 15,
|
||||
claim: 'GET /transformations',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Buscar micro-transformações',
|
||||
'en-us': 'Get micro-transformations',
|
||||
'es-es': 'Obtener micro-transformaciones',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 30,
|
||||
claim: 'PUT /transformations',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Editar micro-transformações',
|
||||
'en-us': 'Edit micro-transformations',
|
||||
'es-es': 'Editar micro-transformaciones',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 6,
|
||||
claim: 'DELETE /transformations',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Excluir micro-transformações',
|
||||
'en-us': 'Remove micro-transformations',
|
||||
'es-es': 'Eliminar micro-transformaciones',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
CATALOG: {
|
||||
CREATE: {
|
||||
seqid: 19,
|
||||
claim: 'POST /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Explorar | Catálogo',
|
||||
'en-us': 'Explore | Catalog',
|
||||
'es-es': 'Explorar | Catalogar',
|
||||
},
|
||||
GET: {
|
||||
seqid: 7,
|
||||
claim: 'GET /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 25,
|
||||
claim: 'PUT /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 1,
|
||||
claim: 'DELETE /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
GET: {
|
||||
seqid: 7,
|
||||
claim: 'GET /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar lista de Ativos e visualizar atributos',
|
||||
'en-us': 'Access Assets list and its attributes',
|
||||
'es-es': 'Acceda a la lista de Activos y vea los atributos',
|
||||
},
|
||||
},
|
||||
CREATE: {
|
||||
seqid: 19,
|
||||
claim: 'catalog:create',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Criar Ativos',
|
||||
'en-us': 'Create Assets',
|
||||
'es-es': 'Crear Activos',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 25,
|
||||
claim: 'catalog:edit',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Criar e editar atributos no catálogo',
|
||||
'en-us': 'Create and edit attributes on the catalog',
|
||||
'es-es': 'Crear y editar atributos en el catálogo',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 1,
|
||||
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,
|
||||
name: {
|
||||
'pt-br':
|
||||
'Gerente do catálogo. Consegue vizualizar e editar todos os ativos.',
|
||||
'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: {
|
||||
CREATE: {
|
||||
seqid: 20,
|
||||
claim: 'POST /connectors',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Conectores',
|
||||
'en-us': 'Connectors',
|
||||
'es-es': 'Conectores',
|
||||
},
|
||||
GET: {
|
||||
seqid: 8,
|
||||
claim: 'GET /connectors',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 26,
|
||||
claim: 'PUT /connectors',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 2,
|
||||
claim: 'DELETE /connectors',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 20,
|
||||
claim: 'POST /connectors',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Criar conectores',
|
||||
'en-us': 'Create connectors',
|
||||
'es-es': 'Crear conectores',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
seqid: 8,
|
||||
claim: 'GET /connectors',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Buscar conectores',
|
||||
'en-us': 'Get connectors',
|
||||
'es-es': 'Obtener conectores',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 26,
|
||||
claim: 'PUT /connectors',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Editar conectores',
|
||||
'en-us': 'Edit connectors',
|
||||
'es-es': 'Editar conectores',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 2,
|
||||
claim: 'DELETE /connectors',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Excluir conectores',
|
||||
'en-us': 'Remove connectors',
|
||||
'es-es': 'Eliminar conectores',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SNOWFLAKE: {
|
||||
OPEN: {
|
||||
seqid: 14,
|
||||
claim: 'GET /snowflake',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Explorar | Consolidar',
|
||||
'en-us': 'Explore | Data consolidation',
|
||||
'es-es': 'Explorar | Consolidación de datos',
|
||||
},
|
||||
permissions: {
|
||||
OPEN: {
|
||||
seqid: 14,
|
||||
claim: 'GET /snowflake',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar Snowflake',
|
||||
'en-us': 'Access Snowflake',
|
||||
'es-es': 'Acceso Snowflake',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
ZENDESK: {
|
||||
OPEN: {
|
||||
seqid: 16,
|
||||
claim: 'GET /zendesk',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
title: {
|
||||
'pt-br': 'Zendesk',
|
||||
'en-us': 'Zendesk',
|
||||
'es-es': 'Zendesk',
|
||||
},
|
||||
permissions: {
|
||||
OPEN: {
|
||||
seqid: 16,
|
||||
claim: 'support-chat:access',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Acessar chat de suporte',
|
||||
'en-us': 'Access support chat',
|
||||
'es-es': 'Acceder al chat de soporte',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
METABASE: {
|
||||
OPEN: {
|
||||
seqid: 11,
|
||||
claim: 'GET /metabase',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
DATAVIZ: {
|
||||
title: {
|
||||
'pt-br': 'Analisar | Visualização',
|
||||
'en-us': 'Analyze | Visualization',
|
||||
'es-es': 'Analizar | Visualización',
|
||||
},
|
||||
permissions: {
|
||||
METABASE: {
|
||||
seqid: 11,
|
||||
claim: 'GET /metabase',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar Metabase',
|
||||
'en-us': 'Access Metabase',
|
||||
'es-es': 'Acceso Metabase',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
ORCHEST: {
|
||||
INTELLIGENCE: {
|
||||
seqid: 31,
|
||||
claim: 'intelligence:open',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
INTELLIGENCE: {
|
||||
title: {
|
||||
'pt-br': 'Analisar | Inteligência',
|
||||
'en-us': 'Analyze | Intelligence',
|
||||
'es-es': 'Analizar | Inteligencia',
|
||||
},
|
||||
APP_BUILDER: {
|
||||
seqid: 32,
|
||||
claim: 'app-builder:open',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
permissions: {
|
||||
INTELLIGENCE: {
|
||||
seqid: 31,
|
||||
claim: 'intelligence:open',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar Módulo de Inteligência',
|
||||
'en-us': 'Access Intelligence Module',
|
||||
'es-s': 'Acceder Módulo de Inteligencia',
|
||||
},
|
||||
},
|
||||
},
|
||||
MACHINE_LEARNING: {
|
||||
seqid: 33,
|
||||
claim: 'machine-learning:open',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
},
|
||||
MODULES: {
|
||||
title: {
|
||||
'pt-br': 'Módulos da Dadosfera',
|
||||
'en-us': 'Dadosfera Modules',
|
||||
'es-es': 'Módulos de la Dadosfera',
|
||||
},
|
||||
permissions: {
|
||||
APP_BUILDER: {
|
||||
seqid: 32,
|
||||
claim: 'app-builder:open',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Acessar App Builder',
|
||||
'en-us': 'Access App Builder',
|
||||
'es-es': 'Acceder App Builder',
|
||||
},
|
||||
},
|
||||
MACHINE_LEARNING: {
|
||||
seqid: 33,
|
||||
claim: 'machine-learning:open',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Acessar Machine Learning',
|
||||
'en-us': 'Access Machine Learning',
|
||||
'es-es': 'Acceder Machine Learning',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
USERS: {
|
||||
title: {
|
||||
'pt-br': 'Usuários',
|
||||
'en-us': 'Users',
|
||||
'es-es': 'Usuarios',
|
||||
},
|
||||
permissions: {
|
||||
ADMIN: {
|
||||
seqid: 34,
|
||||
claim: 'users:admin',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Gestão de usuários e grupos',
|
||||
'en-us': 'Manage user and roles',
|
||||
'es-es': 'Administrar usuarios y roles',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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: {
|
||||
title: {
|
||||
'pt-br': 'Usuários',
|
||||
'en-us': 'Users',
|
||||
'es-es': 'Usuarios',
|
||||
},
|
||||
permissions: {
|
||||
CONNECTIONS_MIGRATION: {
|
||||
seqid: 40,
|
||||
claim: 'dadosfera:connections-migration',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'connections-migration',
|
||||
'en-us': '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)
|
||||
let nextAvailableSeqid = 0;
|
||||
const seqids = Object.values(Permissions).flatMap((namespace) =>
|
||||
Object.values(namespace).map(({ seqid }) => seqid),
|
||||
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||
Object.values(namespace.permissions).map(({ seqid }) => seqid),
|
||||
);
|
||||
const claims = Object.values(Permissions).flatMap((namespace) =>
|
||||
Object.values(namespace).map(({ claim }) => claim),
|
||||
const claims = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||
Object.values(namespace.permissions).map(({ claim }) => claim),
|
||||
);
|
||||
Object.values(Permissions).map((namespace) =>
|
||||
Object.values(namespace).map(({ seqid, claim }) => {
|
||||
Object.values(PERMISSIONS_GROUPS).map((namespace) =>
|
||||
Object.values(namespace.permissions).map(({ seqid, claim }) => {
|
||||
const seqidsCount = seqids.filter((x) => x === seqid).length;
|
||||
assert(seqidsCount === 1, `seqid ${seqid} count is not 1`);
|
||||
|
||||
@@ -241,4 +651,9 @@ Object.values(Permissions).map((namespace) =>
|
||||
}),
|
||||
);
|
||||
|
||||
DADOSFERA_MODULES.map((m) => m.key).forEach((m, i, arr) => {
|
||||
if (arr.indexOf(m) !== i)
|
||||
throw new Error(`DADOSFERA_MODULES[${i}] does not have a unique key`);
|
||||
});
|
||||
|
||||
logger.log(`next available seqid ${nextAvailableSeqid + 1}`);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import { ConnectorManager } from 'protospack-v2';
|
||||
|
||||
export class ConnectorClientConfiguration {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [
|
||||
ConnectorManager.ProtoPackages.WritePackage,
|
||||
ConnectorManager.ProtoPackages.ReadPackage,
|
||||
],
|
||||
credentials:
|
||||
process.env.LOCAL_ENV === 'local'
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: [
|
||||
ConnectorManager.ProtoPaths.WriteFilePath,
|
||||
ConnectorManager.ProtoPaths.ReadFilePath,
|
||||
],
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
import {
|
||||
OnModuleInit,
|
||||
Inject,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { ConnectorManager } from 'protospack-v2';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
export class ConnectorClientService implements OnModuleInit {
|
||||
private connectorServiceRead: ConnectorManager.ReadService.ConnectorManagerReadServices;
|
||||
private connectorServiceWrite: ConnectorManager.WriteService.ConnectorManagerWriteServices;
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('CONNECTOR_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.connectorServiceWrite =
|
||||
this.grpcClient.getService<ConnectorManager.WriteService.ConnectorManagerWriteServices>(
|
||||
ConnectorManager.ProtoServices.ConnectorManagerWriteServices,
|
||||
);
|
||||
|
||||
this.connectorServiceRead =
|
||||
this.grpcClient.getService<ConnectorManager.ReadService.ConnectorManagerReadServices>(
|
||||
ConnectorManager.ProtoServices.ConnectorManagerReadServices,
|
||||
);
|
||||
}
|
||||
|
||||
async uploadConnector(uploadConnector) {
|
||||
const connector: ConnectorManager.Entities.ConnectorCreateRequest = {
|
||||
file: {
|
||||
buffer: uploadConnector.file.buffer,
|
||||
mimetypes: uploadConnector.file.mimetype,
|
||||
},
|
||||
connector: JSON.stringify(uploadConnector.connector),
|
||||
};
|
||||
const serviceBody: ConnectorManager.Messages.RegisterConnectorRequest = {
|
||||
connector,
|
||||
};
|
||||
|
||||
const observable = from(
|
||||
this.connectorServiceWrite.RegisterConnector(serviceBody),
|
||||
);
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.details);
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - Upload');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async uploadFile(uploadFile) {
|
||||
const body: ConnectorManager.Messages.UploadFileRequest = {
|
||||
file: {
|
||||
buffer: uploadFile.file.buffer,
|
||||
mimetypes: uploadFile.file.mimetype,
|
||||
},
|
||||
name: uploadFile.name,
|
||||
};
|
||||
|
||||
const observable = from(this.connectorServiceWrite.UploadFile(body));
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.details);
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - Upload');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getAllConnectors(body) {
|
||||
const observable = from(
|
||||
this.connectorServiceRead.GetAllConnectors({
|
||||
search: body.search,
|
||||
filters: JSON.stringify(body.filters),
|
||||
}),
|
||||
);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - getAllConnectors');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getConnectorDetails(plugin: string) {
|
||||
const observable = from(
|
||||
this.connectorServiceRead.GetConnectorDetails({
|
||||
plugin,
|
||||
}),
|
||||
);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - getAllConnectors');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getConnector(plugin: string) {
|
||||
const observable = from(
|
||||
this.connectorServiceRead.GetConnector({
|
||||
plugin,
|
||||
}),
|
||||
);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - getAllConnectors');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteConnector(plugin: string, version: string) {
|
||||
const observable = from(
|
||||
this.connectorServiceWrite.DeleteConnector({
|
||||
version,
|
||||
plugin,
|
||||
}),
|
||||
);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - getAllConnectors');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async updateConnector({ plugin, changes }) {
|
||||
const observable = from(
|
||||
this.connectorServiceWrite.UpdateConnector({
|
||||
plugin,
|
||||
changes,
|
||||
}),
|
||||
);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
observable.subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
//console.log('done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err);
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.info('ConnectorClientService - getAllConnectors');
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import {
|
||||
ProtoPackages,
|
||||
ProtoPaths,
|
||||
} from 'protospack-v2/dist/lib/Duc';
|
||||
|
||||
export class DucClient {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.DUC_URL,
|
||||
package: ProtoPackages.WritePackage,
|
||||
credentials: process.env.LOCAL_ENV
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: ProtoPaths.WriteFilePath,
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
|
||||
import { InputPackages, InputProtoFilePath } from 'protospack';
|
||||
|
||||
export class InputsClientConfiguration {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: InputPackages,
|
||||
credentials: process.env.LOCAL_ENV
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: InputProtoFilePath,
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
import {
|
||||
OnModuleInit,
|
||||
Inject,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
InternalServerErrorException,
|
||||
} from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import {
|
||||
DecodeGrpcStruct,
|
||||
EncodeJsonToGrpcStruct,
|
||||
InputCreateS3Request,
|
||||
InputNewCreateRequest,
|
||||
InputService,
|
||||
TestConnectionGetColumnsRequest,
|
||||
TestConnectionRequest,
|
||||
} from 'protospack';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { InputModel } from 'src/modules/inputs/dtos/input.model';
|
||||
import {
|
||||
objectCamelToSnake,
|
||||
objectSnakeToCamel,
|
||||
} from 'src/utils/CaseConverter';
|
||||
import { mustache } from 'src/utils/mustache';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { OauthSecrets } from 'src/utils/OauthSecrets';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { IIdRequest, UpdateInputRequest } from './interfaces';
|
||||
|
||||
export class InputsClientService implements OnModuleInit {
|
||||
private inputService: InputService;
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('INPUTS_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
@Inject('OAUTH_SECRETS') private readonly oauthSecrets: OauthSecrets,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.inputService =
|
||||
this.grpcClient.getService<InputService>('InputService');
|
||||
}
|
||||
|
||||
async newCreate(createInputDto: InputNewCreateRequest) {
|
||||
this.logger.info('InputClientService - Create');
|
||||
|
||||
const input = await lastValueFrom(
|
||||
this.inputService.NewCreate(objectSnakeToCamel(createInputDto)),
|
||||
);
|
||||
objectCamelToSnake(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
async createGeneric(createInputGeneric) {
|
||||
this.logger.info('InputClientService - Create');
|
||||
const { info, ...data } = createInputGeneric;
|
||||
// data.credentials = await this.getInputTokens(data);
|
||||
if (data.options) delete data.options;
|
||||
if (data.credentials.oauth_code) delete data.credentials.oauth_code;
|
||||
const grpcPayload = {
|
||||
input: EncodeJsonToGrpcStruct(data),
|
||||
info,
|
||||
};
|
||||
objectSnakeToCamel(grpcPayload);
|
||||
|
||||
const structReturn = await lastValueFrom(
|
||||
this.inputService.Create(grpcPayload),
|
||||
).catch((err: { details: string }) => {
|
||||
if (err.details === 'Item Not found!') {
|
||||
throw new NotFoundException('Input not found');
|
||||
}
|
||||
throw new InternalServerErrorException(err.details);
|
||||
});
|
||||
objectCamelToSnake(structReturn);
|
||||
const inputCreated = DecodeGrpcStruct(structReturn.input);
|
||||
return { input: inputCreated };
|
||||
}
|
||||
|
||||
async createS3Inputs(createInputDto: InputCreateS3Request) {
|
||||
this.logger.info('InputClientService - Create');
|
||||
objectSnakeToCamel(createInputDto);
|
||||
const createInputResponse = await lastValueFrom(
|
||||
this.inputService.CreateS3(createInputDto),
|
||||
).catch((e) => {
|
||||
throw new HttpException(e.details, 500);
|
||||
});
|
||||
this.logger.info('Done');
|
||||
objectCamelToSnake(createInputResponse);
|
||||
return createInputResponse;
|
||||
}
|
||||
|
||||
async findOne(data: IIdRequest) {
|
||||
this.logger.info('InputClientService - FindOne');
|
||||
const findOneInputResponse = await lastValueFrom(
|
||||
this.inputService.FindOne(objectSnakeToCamel(data)),
|
||||
).catch((e) => {
|
||||
this.logger.error(e.details);
|
||||
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
objectCamelToSnake(findOneInputResponse);
|
||||
return findOneInputResponse;
|
||||
}
|
||||
|
||||
async findAll(data) {
|
||||
this.logger.info('InputClientService - FindAll');
|
||||
const findAllInputResponse = await lastValueFrom(
|
||||
this.inputService.FindAll(objectSnakeToCamel(data)),
|
||||
).catch((e) => {
|
||||
this.logger.error(e.details);
|
||||
throw new HttpException(e.details, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
findAllInputResponse.inputs.forEach((input) => objectCamelToSnake(input));
|
||||
|
||||
return findAllInputResponse;
|
||||
}
|
||||
|
||||
async update(updateInputDTO: UpdateInputRequest) {
|
||||
this.logger.info('InputClientService - Update');
|
||||
const updateInputResponse = await new Promise((resolve, reject) => {
|
||||
this.inputService.Update(objectSnakeToCamel(updateInputDTO)).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 updateInputResponse;
|
||||
}
|
||||
|
||||
async remove(idRequest: IIdRequest) {
|
||||
this.logger.info('InputClientService - Remove');
|
||||
|
||||
const removeInputResponse = await new Promise((resolve, reject) => {
|
||||
this.inputService.Remove(objectSnakeToCamel(idRequest)).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 removeInputResponse;
|
||||
}
|
||||
|
||||
async testConnection(data: TestConnectionRequest) {
|
||||
this.logger.info('InputClientService - TestConnection');
|
||||
const testConnectionResponse = await new Promise((resolve, reject) => {
|
||||
this.inputService.NewTestConnection(objectSnakeToCamel(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 testConnectionResponse;
|
||||
}
|
||||
|
||||
async getColumns(data: TestConnectionGetColumnsRequest) {
|
||||
this.logger.info('InputClientService - TestConnection/Get-Columns');
|
||||
const getColumnsResponse = await new Promise((resolve, reject) => {
|
||||
this.inputService.GetColumns(objectSnakeToCamel(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;
|
||||
}
|
||||
async getAuthSecrets(plugin) {
|
||||
switch (plugin) {
|
||||
case 'hubspot':
|
||||
return {
|
||||
client_id: this.oauthSecrets.hubspot.client_id || '',
|
||||
client_secret: this.oauthSecrets.hubspot.client_secret || '',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
async getInputTokens(input: InputModel) {
|
||||
const { credentials } = input;
|
||||
let credentialsTokens = {};
|
||||
switch (credentials.connection_type) {
|
||||
case 'oauth':
|
||||
credentialsTokens = await this.getOauthTokens(input);
|
||||
break;
|
||||
}
|
||||
return { ...credentials, ...credentialsTokens };
|
||||
}
|
||||
async getOauthTokens(input: InputModel) {
|
||||
const tokens: Record<string, any> = {};
|
||||
const { options, credentials } = input;
|
||||
const { oauth } = options;
|
||||
const { get_tokens_url_params, get_tokens_set_response } = oauth;
|
||||
const secrets = await this.getAuthSecrets(input.plugin);
|
||||
const get_tokens_url_params_string = mustache(get_tokens_url_params, {
|
||||
...secrets,
|
||||
...credentials,
|
||||
});
|
||||
let get_tokens_url_params_obj: Record<string, any> = {};
|
||||
try {
|
||||
get_tokens_url_params_obj = JSON.parse(get_tokens_url_params_string);
|
||||
} catch (error) {
|
||||
throw new HttpException('Erro transformando get_tokens_url_params', 400);
|
||||
}
|
||||
let params = {};
|
||||
switch (oauth.content_type) {
|
||||
case 'application/x-www-form-urlencoded':
|
||||
params = new URLSearchParams(get_tokens_url_params_obj);
|
||||
params = params.toString();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const axiosRequestConfig: AxiosRequestConfig = {
|
||||
url: oauth.get_tokens_url,
|
||||
method: oauth.get_tokens_method || 'POST',
|
||||
data: params,
|
||||
headers: { 'content-type': oauth.content_type || 'application/json' },
|
||||
};
|
||||
|
||||
const { data } = await axios(axiosRequestConfig).catch((err) => {
|
||||
this.logger.error(err.response.data);
|
||||
throw new HttpException(err.response.data.message, err.response.status);
|
||||
});
|
||||
for (const key in get_tokens_set_response) {
|
||||
const responseKey = get_tokens_set_response[key];
|
||||
tokens[key] = data[responseKey];
|
||||
}
|
||||
const { redirect_uri } = get_tokens_url_params_obj;
|
||||
tokens.redirect_uri = redirect_uri;
|
||||
return tokens;
|
||||
}
|
||||
|
||||
async getAvailableEntities(data) {
|
||||
this.logger.info('InputClientService - GetAvailableEntities');
|
||||
objectSnakeToCamel(data);
|
||||
const response = await lastValueFrom(
|
||||
this.inputService.GetAvailableEntities(data),
|
||||
).catch((err) => {
|
||||
this.logger.info(err.details);
|
||||
if (err.details && err.details.includes('400'))
|
||||
throw new HttpException('Plugin inválido', HttpStatus.BAD_REQUEST);
|
||||
throw new HttpException(err.details, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
objectCamelToSnake(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { OutputPackages, OutputProtoFilePath } from 'protospack';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
|
||||
export class OutputsClientConfiguration {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.OTFACTORY_URL,
|
||||
package: OutputPackages,
|
||||
credentials: process.env.LOCAL_ENV
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: OutputProtoFilePath,
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import { Controller, Inject, OnModuleInit } from '@nestjs/common';
|
||||
import { ClientGrpc, Payload } from '@nestjs/microservices';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import {
|
||||
OutputServicesNames,
|
||||
OutputsServiceInterface,
|
||||
} from 'protospack';
|
||||
import {
|
||||
objectCamelToSnake,
|
||||
objectSnakeToCamel,
|
||||
} from 'src/utils/CaseConverter';
|
||||
import {
|
||||
ICreateOutputRequest,
|
||||
IIdRequest,
|
||||
IOutputUpdateRequest,
|
||||
} from './interfaces';
|
||||
|
||||
@Controller('output')
|
||||
export class OutputsClientService implements OnModuleInit {
|
||||
private outputService: OutputsServiceInterface;
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('OUTPUTS_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.outputService = this.grpcClient.getService<OutputsServiceInterface>(
|
||||
OutputServicesNames.OutputService,
|
||||
);
|
||||
}
|
||||
|
||||
async create(@Payload() createOutputDto: ICreateOutputRequest) {
|
||||
this.logger.info('OutputClientService - Create');
|
||||
|
||||
const createOutputResponse = await new Promise((resolve, reject) => {
|
||||
this.outputService.Create(objectSnakeToCamel(createOutputDto)).subscribe({
|
||||
next(x) {
|
||||
resolve(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);
|
||||
});
|
||||
|
||||
const convertedItem = objectCamelToSnake(createOutputResponse['output']);
|
||||
createOutputResponse['output'] = convertedItem;
|
||||
return createOutputResponse;
|
||||
}
|
||||
|
||||
async findAll(data) {
|
||||
this.logger.info('OutputClientService - FindAll');
|
||||
|
||||
const findAllOutputResponse = await new Promise((resolve, reject) => {
|
||||
this.outputService.FindAll(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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);
|
||||
});
|
||||
|
||||
const convertedOutputs = findAllOutputResponse['outputs'].map((ot) => {
|
||||
return objectCamelToSnake(ot);
|
||||
});
|
||||
|
||||
return { outputs: convertedOutputs };
|
||||
}
|
||||
|
||||
async findOne(data: IIdRequest) {
|
||||
this.logger.info('OutputClientService - FindOne');
|
||||
|
||||
const findOneOutputResponse = await new Promise((resolve, reject) => {
|
||||
this.outputService.FindOne(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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 objectCamelToSnake(findOneOutputResponse);
|
||||
}
|
||||
|
||||
async update(updateOutPutDTO: IOutputUpdateRequest) {
|
||||
this.logger.info('OutputClientService - Update');
|
||||
|
||||
const updateOutputResponse = await new Promise((resolve, reject) => {
|
||||
this.outputService.Update(objectSnakeToCamel(updateOutPutDTO)).subscribe({
|
||||
next(x) {
|
||||
resolve(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 objectCamelToSnake(updateOutputResponse);
|
||||
}
|
||||
|
||||
async remove(data: IIdRequest) {
|
||||
this.logger.info('OutputClientService - Remove');
|
||||
|
||||
const removeOutputResponse = await new Promise((resolve, reject) => {
|
||||
this.outputService.Remove(objectSnakeToCamel(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 removeOutputResponse;
|
||||
}
|
||||
}
|
||||
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
export interface ICreateOutputRequest {
|
||||
operation: string;
|
||||
name: string;
|
||||
plugin: string;
|
||||
values: Values;
|
||||
}
|
||||
|
||||
export interface Values {
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
interface Info {
|
||||
user_id: string;
|
||||
customer_id: string;
|
||||
}
|
||||
|
||||
export interface IIdRequest {
|
||||
id: string;
|
||||
info: Info;
|
||||
}
|
||||
|
||||
export interface IOutputUpdateRequest {
|
||||
id: string;
|
||||
operation: string;
|
||||
values: Values;
|
||||
plugin: string;
|
||||
name: string;
|
||||
info: Info;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { OnModuleInit, Inject } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
|
||||
import { ProtoServices } from 'protospack-v2/dist/lib/Duc';
|
||||
import { PermissionsProtoService as PermissionsServiceInterface } from 'protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import {
|
||||
Empty,
|
||||
InjectPermissionsRequest,
|
||||
} from 'protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import grpcHandler from '../../utils/grpcHandler';
|
||||
|
||||
export class PermissionsClientService implements OnModuleInit {
|
||||
logger: any;
|
||||
|
||||
private permissionsService: PermissionsServiceInterface;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.permissionsService =
|
||||
this.grpcClient.getService<PermissionsServiceInterface>(
|
||||
ProtoServices.PermissionsProtoService,
|
||||
);
|
||||
}
|
||||
|
||||
async injectPermissions({ permissions }: InjectPermissionsRequest) {
|
||||
this.logger.info('InjectPermissions');
|
||||
|
||||
return grpcHandler<Empty>(
|
||||
this.permissionsService.InjectPermissions({ permissions }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import {
|
||||
PipelinePackages,
|
||||
PipelineProtoFilePath,
|
||||
} from 'protospack';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
|
||||
export class PipelinesClientConfiguration {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.PIFACTORY_URL,
|
||||
package: PipelinePackages,
|
||||
credentials: process.env.LOCAL_ENV
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: PipelineProtoFilePath,
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Inject,
|
||||
InternalServerErrorException,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { ClientGrpc, Payload } from '@nestjs/microservices';
|
||||
import { PipelineServicesNames, PipelinesServiceInterface } from 'protospack';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
objectCamelToSnake,
|
||||
objectSnakeToCamel,
|
||||
} from 'src/utils/CaseConverter';
|
||||
import {
|
||||
ICreatePipelineDto,
|
||||
IGetPipelineLogsRequest,
|
||||
IIdRequest,
|
||||
IUpdatePipelineRequest,
|
||||
} from './interfaces';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
|
||||
export class PipelinesClientService implements OnModuleInit {
|
||||
private pipelineService: PipelinesServiceInterface;
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('PIPELINES_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.pipelineService =
|
||||
this.grpcClient.getService<PipelinesServiceInterface>(
|
||||
PipelineServicesNames.PipelineService,
|
||||
);
|
||||
}
|
||||
|
||||
async create(@Payload() createPipelineDto: ICreatePipelineDto) {
|
||||
this.logger.info('PipelinesClientService - Create');
|
||||
const createPipelineResponse = await lastValueFrom(
|
||||
this.pipelineService.Create(objectSnakeToCamel(createPipelineDto)),
|
||||
)
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((error: { details: string }) => {
|
||||
if (error.details.includes('INVALID_REQUEST')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [errorType, message] = error.details.split('|');
|
||||
throw new BadRequestException(message);
|
||||
}
|
||||
throw new InternalServerErrorException(error.details);
|
||||
});
|
||||
|
||||
return createPipelineResponse;
|
||||
}
|
||||
|
||||
async findAll(data: IIdRequest) {
|
||||
this.logger.info('PipelinesClientService - FindAll');
|
||||
const findAllPipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService.FindAll(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(x.pipelines);
|
||||
},
|
||||
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 findAllPipelineResponse;
|
||||
}
|
||||
|
||||
async findOne(data: IIdRequest) {
|
||||
this.logger.info('PipelinesClientService - FindOne');
|
||||
|
||||
const findOnePipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService.FindOne(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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 findOnePipelineResponse;
|
||||
}
|
||||
|
||||
async update(UpdatePipelineRequest: IUpdatePipelineRequest) {
|
||||
this.logger.info('PipelinesClientService - Update');
|
||||
|
||||
const updatePipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService
|
||||
.Update(objectSnakeToCamel(UpdatePipelineRequest))
|
||||
.subscribe({
|
||||
next(x) {
|
||||
resolve(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 updatePipelineResponse;
|
||||
}
|
||||
|
||||
async remove(data: IIdRequest) {
|
||||
this.logger.info('PipelinesClientService - Remove');
|
||||
|
||||
const removePipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService.remove(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(objectCamelToSnake(x));
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
// this.logger.info('Done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.message);
|
||||
throw new Error(err);
|
||||
});
|
||||
|
||||
return removePipelineResponse;
|
||||
}
|
||||
|
||||
async getPipelineLogsMessages(data: IGetPipelineLogsRequest) {
|
||||
this.logger.info('PipelinesClientService - GetPipelineLogsMessages');
|
||||
|
||||
const logsPipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService.getPipelineLogs(data).subscribe({
|
||||
next(x) {
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
// this.logger.info('Done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.message);
|
||||
throw new Error(err);
|
||||
});
|
||||
|
||||
return logsPipelineResponse;
|
||||
}
|
||||
|
||||
async getPipelineStatus(data) {
|
||||
this.logger.info('PipelinesClientService - GetPipelineStatus');
|
||||
|
||||
const statusPipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService
|
||||
.getPipelineStatus(objectSnakeToCamel(data))
|
||||
.subscribe({
|
||||
next(x) {
|
||||
const statusArray = x.status.sort((a, b) => {
|
||||
if (a.id < b.id) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
resolve({ status: statusArray });
|
||||
},
|
||||
error(err) {
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
// this.logger.info('Done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.message);
|
||||
throw new Error(err);
|
||||
});
|
||||
|
||||
return statusPipelineResponse;
|
||||
}
|
||||
|
||||
async runPipeline({ id, info }: IIdRequest) {
|
||||
this.logger.info('PipelinesClientService - RunPipeline');
|
||||
const statusPipelineResponse = await new Promise((resolve, reject) => {
|
||||
this.pipelineService
|
||||
.triggerPipeline(objectSnakeToCamel({ id, info }))
|
||||
.subscribe({
|
||||
next(x) {
|
||||
if (x.status == false) {
|
||||
reject(
|
||||
'This pipeline is not ready yet to execute, Try again later!',
|
||||
);
|
||||
}
|
||||
resolve(x);
|
||||
},
|
||||
error(err) {
|
||||
this.logger.error('Observable Error');
|
||||
reject(err);
|
||||
},
|
||||
complete() {
|
||||
this.logger.info('Done');
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.message);
|
||||
throw new Error(err);
|
||||
});
|
||||
|
||||
return statusPipelineResponse;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import {
|
||||
TransformationPackages,
|
||||
TransformationProtoFilePath,
|
||||
} from 'protospack';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
|
||||
export class TransformationsClientConfiguration {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: TransformationPackages,
|
||||
credentials: credentials.createSsl(),
|
||||
protoPath: TransformationProtoFilePath,
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
import { Controller, Inject, OnModuleInit, Post } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { TransformationsServiceInterface } from 'protospack';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import {
|
||||
objectCamelToSnake,
|
||||
objectSnakeToCamel,
|
||||
} from 'src/utils/CaseConverter';
|
||||
import { ICreateTransformationsRequest, IIdRequest } from './interfaces';
|
||||
|
||||
@Controller('transformation')
|
||||
export class TransformationsClientService implements OnModuleInit {
|
||||
private transformationService: TransformationsServiceInterface;
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('TRANSFORMATIONS_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.transformationService =
|
||||
this.grpcClient.getService<TransformationsServiceInterface>(
|
||||
'TransformationService',
|
||||
);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(createTransformationsDto: ICreateTransformationsRequest) {
|
||||
this.logger.info('TransformationClientService - Create');
|
||||
|
||||
const createTransformationResponse = await new Promise(
|
||||
(resolve, reject) => {
|
||||
this.transformationService
|
||||
.Create(objectSnakeToCamel(createTransformationsDto))
|
||||
.subscribe({
|
||||
next(x) {
|
||||
resolve(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);
|
||||
});
|
||||
|
||||
const convertedTransforms = createTransformationResponse[
|
||||
'transformations'
|
||||
].map((tr) => {
|
||||
return objectCamelToSnake(tr);
|
||||
});
|
||||
|
||||
return { transformations: convertedTransforms };
|
||||
}
|
||||
|
||||
async findAll(data) {
|
||||
this.logger.info('TransformationClientService - FindAll');
|
||||
|
||||
const findAllTransformationResponse = await new Promise(
|
||||
(resolve, reject) => {
|
||||
this.transformationService.FindAll(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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);
|
||||
});
|
||||
const convertedTransforms = findAllTransformationResponse[
|
||||
'transformations'
|
||||
].map((tr) => {
|
||||
return objectCamelToSnake(tr);
|
||||
});
|
||||
return { transformations: convertedTransforms };
|
||||
}
|
||||
|
||||
async findOne(data: IIdRequest) {
|
||||
this.logger.info('TransformationClientService - FindOne');
|
||||
|
||||
const findOneTransformationResponse = await new Promise(
|
||||
(resolve, reject) => {
|
||||
this.transformationService.FindOne(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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 objectCamelToSnake(findOneTransformationResponse);
|
||||
}
|
||||
|
||||
async update(updateTransformationDTO) {
|
||||
this.logger.info('TransformationClientService - Update');
|
||||
|
||||
const updateTransformationResponse = await new Promise(
|
||||
(resolve, reject) => {
|
||||
this.transformationService
|
||||
.Update(objectSnakeToCamel(updateTransformationDTO))
|
||||
.subscribe({
|
||||
next(x) {
|
||||
resolve(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 objectCamelToSnake(updateTransformationResponse);
|
||||
}
|
||||
|
||||
async remove(data: IIdRequest) {
|
||||
this.logger.info('TransformationClientService - Remove');
|
||||
|
||||
const removeTransformationResponse = await new Promise(
|
||||
(resolve, reject) => {
|
||||
this.transformationService.Remove(objectSnakeToCamel(data)).subscribe({
|
||||
next(x) {
|
||||
resolve(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 objectCamelToSnake(removeTransformationResponse);
|
||||
}
|
||||
}
|
||||
@@ -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,18 +1,19 @@
|
||||
import request from 'supertest';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { Controller, Get, HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { AuthenticationGuard } from './authentication.guard';
|
||||
import { AuthClientService } from '../clients/auth/client.service';
|
||||
|
||||
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 ErrorCodes from '../utils/errorCodes';
|
||||
import { User } from './user.decorator';
|
||||
import { Permissions } from './permissions.enum';
|
||||
|
||||
const logger = {
|
||||
info: (...args) => args,
|
||||
error: (...args) => args,
|
||||
};
|
||||
@Controller('user')
|
||||
class UserController {
|
||||
@Get()
|
||||
@@ -45,10 +46,13 @@ describe('user.decorator', () => {
|
||||
const fakeUserPayload = {
|
||||
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
|
||||
username: 'super.admin',
|
||||
permissions: [Permissions.METABASE.OPEN].map(({ seqid }) => seqid),
|
||||
permissions: [PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE].map(
|
||||
({ seqid }) => seqid,
|
||||
),
|
||||
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
|
||||
customer_name: 'dadosfera',
|
||||
customer_tier: 'BASIC',
|
||||
access_token: '',
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -64,7 +68,7 @@ describe('user.decorator', () => {
|
||||
},
|
||||
{
|
||||
provide: DadosferaLogger,
|
||||
useValue: { logger: console },
|
||||
useValue: { logger },
|
||||
},
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
@@ -170,5 +174,6 @@ describe('user.decorator', () => {
|
||||
UserTest(null);
|
||||
|
||||
const token = CreateToken();
|
||||
fakeUserPayload.access_token = token;
|
||||
UserTest(token);
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
|
||||
|
||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||
import ErrorCodes from '../utils/errorCodes';
|
||||
@@ -10,6 +10,7 @@ export interface RequestUser {
|
||||
customer_id: string;
|
||||
customer_name: string;
|
||||
customer_tier: string;
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export const User: (options?: { required?: boolean }) => ParameterDecorator =
|
||||
@@ -19,6 +20,5 @@ export const User: (options?: { required?: boolean }) => ParameterDecorator =
|
||||
if (!request.user && options?.required) {
|
||||
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return request.user;
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||
|
||||
/**
|
||||
* ExceptionFilter to handle RpcException thrown by our microservices
|
||||
*
|
||||
* If it detects that the exception is already an **HttpException** or it is generated by our **ErrorBuilder**,
|
||||
* it will return the exception without any treatment.
|
||||
*/
|
||||
@Catch(Error)
|
||||
export class GrpcToHttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: any, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
return response
|
||||
.status(exception.getStatus())
|
||||
.json(exception.getResponse());
|
||||
}
|
||||
|
||||
const err =
|
||||
exception.constructor.name === ErrorBuilder.name
|
||||
? exception
|
||||
: new ErrorBuilder(exception.details);
|
||||
|
||||
const { statusCode } = err.response;
|
||||
return response.status(statusCode).json(err.response);
|
||||
}
|
||||
}
|
||||
+54
-31
@@ -1,35 +1,22 @@
|
||||
import 'elastic-apm-node/start';
|
||||
if (process.env.ENV !== 'local') require('elastic-apm-node/start');
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { writeFileSync } from 'fs';
|
||||
import helmet from 'helmet';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
|
||||
import documentEmpty from '../swagger_empty.json';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
import { writeFileSync } from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
async function bootstrap() {
|
||||
DadosferaLogger.setupLogger({
|
||||
serviceName: 'maestro',
|
||||
serviceEnvironment: process.env.ENV,
|
||||
});
|
||||
|
||||
const logger = new DadosferaLogger().logger;
|
||||
|
||||
const orginalWinstonLog = logger.log.bind(logger);
|
||||
logger.log = function (...kwargs: any[]) {
|
||||
if (kwargs[0] in logger.levels) {
|
||||
if (kwargs[0] == 'error') {
|
||||
orginalWinstonLog('error', kwargs[2]);
|
||||
}
|
||||
} else {
|
||||
orginalWinstonLog('info', ...kwargs);
|
||||
}
|
||||
};
|
||||
const logger = new DadosferaLogger();
|
||||
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: logger,
|
||||
logger,
|
||||
cors: {
|
||||
origin: '*',
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
@@ -37,24 +24,60 @@ async function bootstrap() {
|
||||
optionsSuccessStatus: 204,
|
||||
},
|
||||
});
|
||||
|
||||
app.use(helmet());
|
||||
configureSwagger(app);
|
||||
await app.listen(3333);
|
||||
if (process.env.KILL_AFTER_START) await app.close();
|
||||
}
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Maestro')
|
||||
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 swaggerConfig = new DocumentBuilder()
|
||||
.setTitle(swaggerTitle)
|
||||
.setDescription('Documentation for Maestro gateway')
|
||||
.setVersion('1.0')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
.addSecurity('access-token', {
|
||||
type: 'apiKey',
|
||||
name: 'Authorization',
|
||||
in: 'header',
|
||||
})
|
||||
.setDescription('This is the Maestro API');
|
||||
|
||||
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||
swaggerConfig.addServer('https://maestro.dadosfera.ai', 'Dadosfera API');
|
||||
|
||||
const config = swaggerConfig.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
|
||||
if (process.env.ENV != 'stg' && process.env.ENV != 'prd')
|
||||
SwaggerModule.setup('api', app, document);
|
||||
const swaggerJsonFile =
|
||||
process.env.INTERNAL_SWAGGER === 'true'
|
||||
? 'docsfera.json'
|
||||
: 'docsfera.external.json';
|
||||
|
||||
const swaggerDoc = process.env.ENV === 'dev' ? document : documentEmpty;
|
||||
SwaggerModule.setup('api', app, swaggerDoc);
|
||||
if (process.env.ENV === 'local') SwaggerModule.setup('api', app, document);
|
||||
|
||||
await app.listen(3333);
|
||||
writeFileSync(
|
||||
swaggerJsonFile,
|
||||
JSON.stringify(
|
||||
{
|
||||
service: '${DOCS_API_TOKEN}',
|
||||
pageId: '${DOCS_PAGE_ID}',
|
||||
blockId: '${DOCS_BLOCK_ID}',
|
||||
schema: document,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}
|
||||
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 };
|
||||
}
|
||||
```
|
||||
@@ -5,13 +5,21 @@ import {
|
||||
Post,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
OnApplicationBootstrap,
|
||||
Inject,
|
||||
UseFilters,
|
||||
Get,
|
||||
UseGuards,
|
||||
Redirect,
|
||||
Req,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
AuthSignInRequest,
|
||||
AuthRefreshAccessTokenRequest,
|
||||
ApiHeaders,
|
||||
ApiOkResponse,
|
||||
ApiSecurity,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import {
|
||||
AuthChangePasswordRequest,
|
||||
AuthResetPasswordRequest,
|
||||
AuthVerifyResetPasswordCodeRequest,
|
||||
@@ -19,70 +27,105 @@ import {
|
||||
AuthEnableTotpMfaRequest,
|
||||
AuthDisableTotpMfaRequest,
|
||||
AuthVerifyTotpMfaRequest,
|
||||
} from 'protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
|
||||
import { AuthClientService } from '../../clients/auth/client.service';
|
||||
import { PermissionsClientService } from '../../clients/permissions/client.service';
|
||||
import { Permissions } from '../../authentication/permissions.enum';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import ErrorBuilder from '../../utils/ErrorBuilder';
|
||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||
import {
|
||||
Authenticated,
|
||||
RequireAllPermissions,
|
||||
} from 'src/decorators/authentication.decorator';
|
||||
import { AuthClientService } from './auth.service';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { GrpcToHttpExceptionFilter } from '../../error/grpc-to-http-exception.filter';
|
||||
import { RequestUser, User } from 'src/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')
|
||||
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
@Controller('auth')
|
||||
export class AuthController implements OnApplicationBootstrap {
|
||||
logger: any;
|
||||
export class AuthController {
|
||||
logger: DadosferaLogger;
|
||||
redirectUrl: string;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private authClient: AuthClientService,
|
||||
private permissionsClient: PermissionsClientService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
// internally used to send permissions to duc on microservice startup
|
||||
async onApplicationBootstrap() {
|
||||
this.logger.info('sending permissions to DUC...');
|
||||
|
||||
const permissions = Object.values(Permissions).flatMap((namespace) =>
|
||||
Object.values(namespace),
|
||||
);
|
||||
|
||||
return this.permissionsClient
|
||||
.injectPermissions({ permissions })
|
||||
.catch((err: ErrorBuilder) => {
|
||||
if (
|
||||
err.code === 'No connection established' &&
|
||||
process.env.LOCAL_ENV === 'true'
|
||||
) {
|
||||
return this.logger.info(
|
||||
"couldn't connect to DUC. suppresing in local env",
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
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 }: AuthSignInRequest) {
|
||||
async signIn(
|
||||
@Body() { username, password, totp }: AuthSignInReq,
|
||||
@Language() language: LanguageEnum,
|
||||
): Promise<AuthSignInRes> {
|
||||
this.logger.info('/auth - SignIn');
|
||||
|
||||
return this.authClient.signIn({ username, password, totp });
|
||||
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() { refreshToken }: AuthRefreshAccessTokenRequest,
|
||||
@Body() body: AuthRefreshAccessTokenReq,
|
||||
@Language() language: LanguageEnum,
|
||||
) {
|
||||
this.logger.info('/auth - RefreshAccessToken');
|
||||
const { refreshToken, customerName: customer_name } = body;
|
||||
|
||||
return this.authClient.refreshAccessToken({ refreshToken });
|
||||
const metadata = PackTheMetadata({
|
||||
customer_name,
|
||||
language,
|
||||
});
|
||||
|
||||
return this.authClient.refreshAccessToken({ refreshToken }, metadata);
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('/sso/snowflake')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.SNOWFLAKE.permissions.OPEN)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async snowflakeSignIn(
|
||||
@User() user: RequestUser,
|
||||
@Body('RelayState') relayState: string,
|
||||
) {
|
||||
this.logger.info('/auth - snowflakeSignIn');
|
||||
|
||||
return this.authClient.snowflakeSignIn({
|
||||
userId: user.user_id,
|
||||
relayState,
|
||||
});
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('change-password')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async changePassword(
|
||||
@@ -101,16 +144,24 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
});
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('reset-password')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async resetPassword(@Body() body: AuthResetPasswordRequest) {
|
||||
async resetPassword(
|
||||
@Body() body: AuthResetPasswordRequest,
|
||||
@Language() language: LanguageEnum,
|
||||
) {
|
||||
this.logger.info('/auth - reset-password');
|
||||
const metadata = PackTheMetadata({
|
||||
language: language,
|
||||
});
|
||||
|
||||
const { username } = body;
|
||||
|
||||
return this.authClient.resetPassword({ username });
|
||||
return this.authClient.resetPassword({ username }, metadata);
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('verify-reset-password-code')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async verifyResetPasswordCode(
|
||||
@@ -123,6 +174,7 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
return this.authClient.verifyResetPasswordCode({ username, code });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('confirm-reset-password')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
|
||||
@@ -137,6 +189,7 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
});
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('enable-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async enableTotpMFA(
|
||||
@@ -151,6 +204,7 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
return this.authClient.enableTotpMFA({ accessToken, password });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('disable-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async disableTotpMFA(
|
||||
@@ -165,6 +219,7 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
return this.authClient.disableTotpMFA({ accessToken, password });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('dismiss-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async dismissTotpMFA(@Headers() headers) {
|
||||
@@ -175,6 +230,7 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
return this.authClient.dismissTotpMFA({ accessToken });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('verify-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
|
||||
@@ -185,4 +241,107 @@ export class AuthController implements OnApplicationBootstrap {
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthClientService } from './auth.service';
|
||||
|
||||
import { DucClient } from '../duc/client.config';
|
||||
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
|
||||
import { getOauthSecrets } from 'src/utils/OauthSecrets';
|
||||
const client = new DucClient();
|
||||
|
||||
@Module({
|
||||
imports: [ClientsModule.register([client.providerOptions])],
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
AuthClientService,
|
||||
DadosferaLogger,
|
||||
GoogleLoginStrategy,
|
||||
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
|
||||
],
|
||||
exports: [AuthClientService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -1,43 +1,35 @@
|
||||
import { OnModuleInit, Inject } from '@nestjs/common';
|
||||
import { OnModuleInit, Inject, Injectable } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { ProtoServices } from 'protospack-v2/dist/lib/Duc';
|
||||
import { AuthProtoService as AuthServiceInterface } from 'protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||
import { AuthProtoService as AuthServiceInterface } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import {
|
||||
AuthGetPublicKeysResponse,
|
||||
AuthSnowflakeSignInRequest,
|
||||
AuthSignInRequest,
|
||||
AuthSignInResponse,
|
||||
AuthRefreshAccessTokenRequest,
|
||||
AuthRefreshAccessTokenResponse,
|
||||
AuthEnableTotpMfaRequest,
|
||||
AuthEnableTotpMfaResponse,
|
||||
AuthDisableTotpMfaRequest,
|
||||
AuthDisableTotpMfaResponse,
|
||||
AuthDismissTotpMfaRequest,
|
||||
AuthDismissTotpMfaResponse,
|
||||
AuthVerifyTotpMfaRequest,
|
||||
AuthVerifyTotpMfaResponse,
|
||||
AuthChangePasswordRequest,
|
||||
AuthChangePasswordResponse,
|
||||
AuthResetPasswordRequest,
|
||||
AuthResetPasswordResponse,
|
||||
AuthVerifyResetPasswordCodeRequest,
|
||||
AuthVerifyResetPasswordCodeResponse,
|
||||
AuthConfirmResetPasswordRequest,
|
||||
AuthConfirmResetPasswordResponse,
|
||||
} from 'protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
|
||||
import grpcHandler from '../../utils/grpcHandler';
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
import { DucClient } from '../duc/client.config';
|
||||
import { Metadata } from '@grpc/grpc-js';
|
||||
|
||||
@Injectable()
|
||||
export class AuthClientService implements OnModuleInit {
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
|
||||
private authService: AuthServiceInterface;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
@@ -51,24 +43,34 @@ export class AuthClientService implements OnModuleInit {
|
||||
async getPublicKeys() {
|
||||
this.logger.info('GetPublicKeys');
|
||||
|
||||
return grpcHandler<AuthGetPublicKeysResponse>(
|
||||
this.authService.AuthGetPublicKeys({}),
|
||||
);
|
||||
return lastValueFrom(this.authService.AuthGetPublicKeys({}));
|
||||
}
|
||||
|
||||
async signIn({ username, password, totp }: AuthSignInRequest) {
|
||||
async snowflakeSignIn(input: AuthSnowflakeSignInRequest) {
|
||||
this.logger.info('snowflakeSignIn');
|
||||
|
||||
return lastValueFrom(this.authService.AuthSnowflakeSignIn(input));
|
||||
}
|
||||
|
||||
async signIn(
|
||||
{ username, password, totp }: AuthSignInRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('SignIn');
|
||||
|
||||
return grpcHandler<AuthSignInResponse>(
|
||||
this.authService.AuthSignIn({ username, password, totp }),
|
||||
return lastValueFrom(
|
||||
this.authService.AuthSignIn({ username, password, totp }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
async refreshAccessToken({ refreshToken }: AuthRefreshAccessTokenRequest) {
|
||||
async refreshAccessToken(
|
||||
{ refreshToken }: AuthRefreshAccessTokenRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('RefreshAccessToken');
|
||||
|
||||
return grpcHandler<AuthRefreshAccessTokenResponse>(
|
||||
this.authService.AuthRefreshAccessToken({ refreshToken }),
|
||||
return lastValueFrom(
|
||||
this.authService.AuthRefreshAccessToken({ refreshToken }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +81,7 @@ export class AuthClientService implements OnModuleInit {
|
||||
}: AuthChangePasswordRequest) {
|
||||
this.logger.info('ChangePassword');
|
||||
|
||||
return grpcHandler<AuthChangePasswordResponse>(
|
||||
return lastValueFrom(
|
||||
this.authService.AuthChangePassword({
|
||||
accessToken,
|
||||
oldPassword,
|
||||
@@ -88,11 +90,14 @@ export class AuthClientService implements OnModuleInit {
|
||||
);
|
||||
}
|
||||
|
||||
async resetPassword({ username }: AuthResetPasswordRequest) {
|
||||
async resetPassword(
|
||||
{ username }: AuthResetPasswordRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('resetPassword');
|
||||
|
||||
return grpcHandler<AuthResetPasswordResponse>(
|
||||
this.authService.AuthResetPassword({ username }),
|
||||
return lastValueFrom(
|
||||
this.authService.AuthResetPassword({ username }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ export class AuthClientService implements OnModuleInit {
|
||||
}: AuthVerifyResetPasswordCodeRequest) {
|
||||
this.logger.info('verifyResetPasswordCode');
|
||||
|
||||
return grpcHandler<AuthVerifyResetPasswordCodeResponse>(
|
||||
return lastValueFrom(
|
||||
this.authService.AuthVerifyResetPasswordCode({ username, code }),
|
||||
);
|
||||
}
|
||||
@@ -114,7 +119,7 @@ export class AuthClientService implements OnModuleInit {
|
||||
}: AuthConfirmResetPasswordRequest) {
|
||||
this.logger.info('confirmResetPassword');
|
||||
|
||||
return grpcHandler<AuthConfirmResetPasswordResponse>(
|
||||
return lastValueFrom(
|
||||
this.authService.AuthConfirmResetPassword({
|
||||
username,
|
||||
code,
|
||||
@@ -126,7 +131,7 @@ export class AuthClientService implements OnModuleInit {
|
||||
async enableTotpMFA({ accessToken, password }: AuthEnableTotpMfaRequest) {
|
||||
this.logger.info('enableTotpMFA');
|
||||
|
||||
return grpcHandler<AuthEnableTotpMfaResponse>(
|
||||
return lastValueFrom(
|
||||
this.authService.AuthEnableTotpMfa({ accessToken, password }),
|
||||
);
|
||||
}
|
||||
@@ -134,7 +139,7 @@ export class AuthClientService implements OnModuleInit {
|
||||
async disableTotpMFA({ accessToken, password }: AuthDisableTotpMfaRequest) {
|
||||
this.logger.info('disableTotpMFA');
|
||||
|
||||
return grpcHandler<AuthDisableTotpMfaResponse>(
|
||||
return lastValueFrom(
|
||||
this.authService.AuthDisableTotpMfa({ accessToken, password }),
|
||||
);
|
||||
}
|
||||
@@ -142,16 +147,25 @@ export class AuthClientService implements OnModuleInit {
|
||||
async dismissTotpMFA({ accessToken }: AuthDismissTotpMfaRequest) {
|
||||
this.logger.info('dismissTotpMFA');
|
||||
|
||||
return grpcHandler<AuthDismissTotpMfaResponse>(
|
||||
this.authService.AuthDismissTotpMfa({ accessToken }),
|
||||
);
|
||||
return lastValueFrom(this.authService.AuthDismissTotpMfa({ accessToken }));
|
||||
}
|
||||
|
||||
async verifyTotp({ accessToken, totp }: AuthVerifyTotpMfaRequest) {
|
||||
this.logger.info('disableTotpMFA');
|
||||
|
||||
return grpcHandler<AuthVerifyTotpMfaResponse>(
|
||||
return lastValueFrom(
|
||||
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 {
|
||||
@ApiProperty()
|
||||
@@ -10,3 +15,106 @@ export class LoginDto {
|
||||
@ApiProperty({ required: false })
|
||||
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
@@ -0,0 +1,26 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { CatalogController } from './catalog.controller';
|
||||
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({
|
||||
imports: [
|
||||
ClientsModule.register([client.providerOptions]),
|
||||
OldPipelineModule,
|
||||
UsersModule,
|
||||
RolesModule,
|
||||
],
|
||||
controllers: [CatalogController],
|
||||
providers: [CatalogService, DadosferaLogger],
|
||||
exports: [CatalogService],
|
||||
})
|
||||
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 { 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) {
|
||||
const customer = body.info.customer.toLowerCase();
|
||||
|
||||
@@ -8,138 +63,228 @@ class CatalogService {
|
||||
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) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/all/`);
|
||||
async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
if (!data.embed) data.embed = undefined;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
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 lastValueFrom(
|
||||
this.catalogWriteService.CreateDataAsset(data, metadata),
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getAllDashboardMetabase(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(
|
||||
`${nimbusUrl}/api/catalog/dashboard-metabase/`,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
async managePermissions(data: Messages.ManagePermissionRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
|
||||
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 lastValueFrom(
|
||||
this.catalogWriteService.ManagePermission(data, metadata),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||
);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async getDataStatus(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-status/`);
|
||||
return data;
|
||||
async revokePermissions(data: Messages.RevokePermissionRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
|
||||
return lastValueFrom(
|
||||
this.catalogWriteService.RevokePermission(data, metadata),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async createDataStatus(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.post(
|
||||
`${nimbusUrl}/api/catalog/data-status/`,
|
||||
body,
|
||||
async commentOnDataAsset(data: Messages.MakeACommentRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
|
||||
return lastValueFrom(
|
||||
this.catalogWriteService.MakeAComment(data, metadata),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteComment(data: Messages.UpdateACommentRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
|
||||
return lastValueFrom(
|
||||
this.catalogWriteService.UpdateAComment(data, metadata),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 404,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteDataAsset(data: Messages.DeleteDataAssetRequest, metadata) {
|
||||
this.logger.info('CatalogService - Manage Data assets permissions');
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async getDataDescription(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(
|
||||
`${nimbusUrl}/api/catalog/data-description/`,
|
||||
const result = JSON.parse(data_assets);
|
||||
|
||||
const response = await this.getAssetsUsersAndRoles(
|
||||
result.data_assets,
|
||||
customer_id,
|
||||
);
|
||||
return data;
|
||||
|
||||
return { data_assets: response, total };
|
||||
}
|
||||
|
||||
async createDataDescription(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.post(
|
||||
`${nimbusUrl}/api/catalog/data-description/`,
|
||||
body,
|
||||
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,
|
||||
),
|
||||
);
|
||||
return data;
|
||||
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(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-docs/`);
|
||||
return data;
|
||||
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) {
|
||||
@@ -151,111 +296,129 @@ class CatalogService {
|
||||
return data;
|
||||
}
|
||||
|
||||
async getDataRating(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/data-rating/`);
|
||||
return data;
|
||||
}
|
||||
async findAllTags(data, metadata) {
|
||||
this.logger.info('CatalogService - findAllCustomerTags');
|
||||
|
||||
async createDataRating(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.post(
|
||||
`${nimbusUrl}/api/catalog/data-rating/`,
|
||||
body,
|
||||
const response = await lastValueFrom(
|
||||
this.catalogReadService.GetCustomerTags(data, metadata),
|
||||
)
|
||||
.then((res) => {
|
||||
this.logger.info('Done');
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err.message);
|
||||
throw new Error(err);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
async getAssetsUsersAndRoles(data_assets: Array<any>, customer_id: string) {
|
||||
const { users: customer_users } =
|
||||
await this.userService.findAllUsersByCustomerId(customer_id);
|
||||
const { roles: customer_roles } = await this.roleService.roleSearch(
|
||||
{},
|
||||
{ customer_id },
|
||||
);
|
||||
return data;
|
||||
}
|
||||
return data_assets.map((data_asset) => {
|
||||
const owner = customer_users.find(
|
||||
(u) => u.id === data_asset.owner,
|
||||
)?.username;
|
||||
|
||||
async getSummaryRating(id, body) {
|
||||
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,
|
||||
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;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async createTags(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.post(`${nimbusUrl}/api/catalog/tags/`, body);
|
||||
return data;
|
||||
}
|
||||
|
||||
async findAllTags(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(`${nimbusUrl}/api/catalog/tags/`, body);
|
||||
return data;
|
||||
}
|
||||
|
||||
async findAllTableTags(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(
|
||||
`${nimbusUrl}/api/catalog/table-tags/`,
|
||||
body,
|
||||
async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) {
|
||||
const { session } = await lastValueFrom(
|
||||
this.catalogWriteService.TriggerDatasetCataloging(data, metadata),
|
||||
);
|
||||
return data;
|
||||
return session;
|
||||
}
|
||||
|
||||
async deleteTags(id, body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.delete(`${nimbusUrl}/api/catalog/tags/${id}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
async createTableTags(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.post(
|
||||
`${nimbusUrl}/api/catalog/table-tags/`,
|
||||
body,
|
||||
async getDatasetCatalogTask(session: string, metadata: Metadata) {
|
||||
const res = await lastValueFrom(
|
||||
this.catalogReadService.GetDatasetCatalogTask({ session }, metadata),
|
||||
);
|
||||
return data;
|
||||
return res;
|
||||
}
|
||||
|
||||
async deleteTableTags(id, body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.delete(
|
||||
`${nimbusUrl}/api/catalog/table-tags/${id}`,
|
||||
async addRlsRule(data: AddRlsRuleRequest, metadata: Metadata) {
|
||||
const res = await lastValueFrom(
|
||||
this.catalogWriteService.AddRlsRule(data, metadata),
|
||||
);
|
||||
return data;
|
||||
return res;
|
||||
}
|
||||
|
||||
async getTableRules(body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.get(
|
||||
`${nimbusUrl}/api/catalog/table-rules/`,
|
||||
body,
|
||||
async removeRlsRule(id: number, metadata: Metadata) {
|
||||
const res = await lastValueFrom(
|
||||
this.catalogWriteService.RemoveRlsRule({ id }, metadata),
|
||||
);
|
||||
return data;
|
||||
return res;
|
||||
}
|
||||
|
||||
async deleteTableRules(id, body) {
|
||||
const nimbusUrl = this._getNimbusUrl(body);
|
||||
const { data } = await axios.delete(
|
||||
`${nimbusUrl}/api/catalog/table-rules/${id}`,
|
||||
async batchRemoveRlsRule(
|
||||
query: BatchRemoveRlsRulesRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
const { id_rls, nimbus_dashboard_id } = query;
|
||||
|
||||
if (id_rls && nimbus_dashboard_id) {
|
||||
throw new BadRequestException(
|
||||
"You can't delete using both parameters. Choose either 'id_rls' or 'nimbus_dashboard_id'",
|
||||
);
|
||||
}
|
||||
if (id_rls) {
|
||||
await lastValueFrom(
|
||||
this.catalogWriteService.RemoveRlsRulesByRlsId({ id_rls }, metadata),
|
||||
);
|
||||
} else if (nimbus_dashboard_id) {
|
||||
await lastValueFrom(
|
||||
this.catalogWriteService.RemoveRlsRulesByDashboardId(
|
||||
{ nimbus_dashboard_id: parseInt(nimbus_dashboard_id) },
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
async getRlsRules(data: GetRlsRulesRequest, metadata: Metadata) {
|
||||
const res = await lastValueFrom(
|
||||
this.catalogReadService.GetRlsRules(data, metadata),
|
||||
);
|
||||
return data;
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
ClientProviderOptions,
|
||||
GrpcOptions,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
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 {
|
||||
private config: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [ConnectionTest.ProtoPackages.ReadPackage],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [ConnectionTest.ProtoPaths.ReadFilePath],
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
providerOptions: ClientProviderOptions = {
|
||||
name: 'ConnectionTestGrpcClient',
|
||||
...this.config,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Post,
|
||||
UseFilters,
|
||||
ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { User, RequestUser } from 'src/decorators/user.decorator';
|
||||
import { ConnectionTestService } from './connection-test.service';
|
||||
import {
|
||||
ConnectionTestPingRes,
|
||||
ConnectionTestPingReq,
|
||||
ConnectionTestCredentialsReq,
|
||||
ConnectionTestCredentialsRes,
|
||||
ConnectionTestListSchemasReq,
|
||||
ConnectionTestListSchemasRes,
|
||||
ConnectionTestListTablesReq,
|
||||
ConnectionTestListTablesRes,
|
||||
GetTableMetadataRes,
|
||||
GetTableMetadataReq,
|
||||
} from './dto/connection-test';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { Authenticated } from 'src/decorators/authentication.decorator';
|
||||
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
|
||||
|
||||
@ApiInternalOnlyController()
|
||||
@ApiTags('Connection Test')
|
||||
@Controller('connection-test')
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
@Authenticated()
|
||||
export class ConnectionTestController {
|
||||
logger: any;
|
||||
constructor(
|
||||
private connectionTestService: ConnectionTestService,
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
@Post('ping')
|
||||
@ApiOkResponse({ type: ConnectionTestPingRes })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async connectionTestPing(
|
||||
@User() user: RequestUser,
|
||||
@Body(new ValidationPipe()) body: ConnectionTestPingReq,
|
||||
) {
|
||||
this.logger.info('/connection-test/ping', {
|
||||
user: user.user_id,
|
||||
customer: user.customer_name,
|
||||
});
|
||||
return this.connectionTestService.connectionTestPing(body, user);
|
||||
}
|
||||
|
||||
@Post('credentials')
|
||||
@ApiOkResponse({ type: ConnectionTestCredentialsRes })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async connectionTestCredentials(
|
||||
@User() user: RequestUser,
|
||||
@Body() body: ConnectionTestCredentialsReq,
|
||||
) {
|
||||
this.logger.info('/connection-test/credentials', {
|
||||
user: user.user_id,
|
||||
customer: user.customer_name,
|
||||
});
|
||||
return this.connectionTestService.connectionTestCredentials(body, user);
|
||||
}
|
||||
|
||||
@Post('schemas')
|
||||
@ApiOkResponse({ type: ConnectionTestListSchemasRes })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async connectionTestListSchemas(
|
||||
@User() user: RequestUser,
|
||||
@Body(new ValidationPipe()) body: ConnectionTestListSchemasReq,
|
||||
) {
|
||||
this.logger.info('/connection-test/schemas', {
|
||||
user: user.user_id,
|
||||
customer: user.customer_name,
|
||||
});
|
||||
return this.connectionTestService.connectionTestListSchemas(
|
||||
body,
|
||||
user.customer_name,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('tables')
|
||||
@ApiOkResponse({ type: ConnectionTestListTablesRes })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async connectionTestListTables(
|
||||
@User() user: RequestUser,
|
||||
@Body(new ValidationPipe()) body: ConnectionTestListTablesReq,
|
||||
) {
|
||||
this.logger.info('/connection-test/tables', {
|
||||
user: user.user_id,
|
||||
customer: user.customer_name,
|
||||
});
|
||||
return this.connectionTestService.connectionTestListTables(
|
||||
body,
|
||||
user.customer_name,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('table-metadata')
|
||||
@ApiOkResponse({ type: GetTableMetadataRes })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getTableMetadata(
|
||||
@User() user: RequestUser,
|
||||
@Body(new ValidationPipe()) body: GetTableMetadataReq,
|
||||
) {
|
||||
this.logger.info('/connection-test/table-metadata', {
|
||||
user: user.user_id,
|
||||
customer: user.customer_name,
|
||||
});
|
||||
return this.connectionTestService.getTableMetadata(
|
||||
body,
|
||||
user.customer_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConnectionTestController } from './connection-test.controller';
|
||||
import { ConnectionTestService } from './connection-test.service';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { ConnectionTestClientConfiguration } from './connection-test-client.config';
|
||||
import { ConnectionModule } from '../connection/connection.module';
|
||||
const client = new ConnectionTestClientConfiguration();
|
||||
@Module({
|
||||
controllers: [ConnectionTestController],
|
||||
providers: [ConnectionTestService, DadosferaLogger],
|
||||
imports: [ClientsModule.register([client.providerOptions]), ConnectionModule],
|
||||
})
|
||||
export class ConnectionTestModule {}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { ConnectionTest } from '@dadosfera/protospack-v2';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
ConnectionTestCredentialsReq,
|
||||
ConnectionTestCredentialsRes,
|
||||
ConnectionTestListSchemasReq,
|
||||
ConnectionTestListSchemasRes,
|
||||
ConnectionTestListTablesReq,
|
||||
ConnectionTestListTablesRes,
|
||||
ConnectionTestPingReq,
|
||||
ConnectionTestPingRes,
|
||||
GetTableMetadataReq,
|
||||
GetTableMetadataRes,
|
||||
} from './dto/connection-test';
|
||||
import { ConnectionClientService } from '../connection/client.service';
|
||||
import {
|
||||
CreateConnectionDto,
|
||||
DatabaseConnectionPropertiesDto,
|
||||
} from '../connection/dtos/connection';
|
||||
import { RequestUser } from 'src/decorators/user.decorator';
|
||||
import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
|
||||
|
||||
@Injectable()
|
||||
export class ConnectionTestService {
|
||||
private connectionTestReadClient: ConnectionTest.ReadService.ConnectionTestReadServices;
|
||||
constructor(
|
||||
@Inject('ConnectionTestGrpcClient') private readonly grpcClient: ClientGrpc,
|
||||
private connectionsService: ConnectionClientService,
|
||||
) {
|
||||
this.connectionTestReadClient =
|
||||
grpcClient.getService<ConnectionTest.ReadService.ConnectionTestReadServices>(
|
||||
ConnectionTest.ProtoServices.ConnectionTestReadServices,
|
||||
);
|
||||
}
|
||||
|
||||
async connectionTestPing(
|
||||
body: ConnectionTestPingReq,
|
||||
user: RequestUser,
|
||||
): Promise<ConnectionTestPingRes> {
|
||||
const {
|
||||
host: endpoint,
|
||||
plugin,
|
||||
port,
|
||||
network_config_id,
|
||||
connection_id,
|
||||
} = body;
|
||||
const { user_id, customer_id, customer_name } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
customer_name,
|
||||
customer_id,
|
||||
user_id,
|
||||
});
|
||||
|
||||
if (connection_id) {
|
||||
const con = await this.connectionsService.getConnectionDetails({
|
||||
body,
|
||||
metadata,
|
||||
});
|
||||
const conPayload = JSON.parse(con.connection.properties);
|
||||
const { plugin, network_config_id } = con.connection;
|
||||
const { host, port, endpoint } = conPayload;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionTestReadClient.ConnectionTestPing({
|
||||
customer_name,
|
||||
endpoint: endpoint || host,
|
||||
network_config_id,
|
||||
plugin,
|
||||
port: port ? port.toString() : undefined,
|
||||
connection_id,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return lastValueFrom(
|
||||
this.connectionTestReadClient.ConnectionTestPing({
|
||||
customer_name,
|
||||
endpoint,
|
||||
network_config_id,
|
||||
plugin,
|
||||
port: port ? port.toString() : undefined,
|
||||
connection_id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
async connectionTestCredentials(
|
||||
body: ConnectionTestCredentialsReq,
|
||||
user: RequestUser,
|
||||
): Promise<ConnectionTestCredentialsRes> {
|
||||
const { plugin, ...connectionBody } = body;
|
||||
let { connection_id } = body;
|
||||
const { customer_id, customer_name, user_id, username } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
customer_id,
|
||||
customer_name,
|
||||
user_id,
|
||||
username,
|
||||
});
|
||||
|
||||
if (body.network_config_id == '') {
|
||||
body.network_config_id = undefined;
|
||||
}
|
||||
|
||||
/********* CREATE CONNECTION *********/
|
||||
if (!connection_id) {
|
||||
const properties: DatabaseConnectionPropertiesDto = {
|
||||
...connectionBody.properties,
|
||||
plugin,
|
||||
credentials_type: 'basic_auth',
|
||||
};
|
||||
const createConnectionBody: CreateConnectionDto = {
|
||||
...body,
|
||||
properties,
|
||||
};
|
||||
const { connection } = await this.connectionsService.createConnection(
|
||||
createConnectionBody,
|
||||
metadata,
|
||||
);
|
||||
connection_id = connection.id;
|
||||
}
|
||||
/************************************/
|
||||
const { operation_result } = await lastValueFrom(
|
||||
this.connectionTestReadClient.ConnectionTestCredentials({
|
||||
connection_id,
|
||||
customer_name,
|
||||
plugin,
|
||||
}),
|
||||
).catch(async (e) => {
|
||||
if (!body.connection_id) {
|
||||
await this.connectionsService.deleteConnection({
|
||||
body: { id: connection_id },
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
if (!operation_result && !body.connection_id) {
|
||||
await this.connectionsService.deleteConnection({
|
||||
body: { id: connection_id },
|
||||
metadata,
|
||||
});
|
||||
connection_id = null;
|
||||
}
|
||||
return { operation_result, connection_id };
|
||||
}
|
||||
async connectionTestListSchemas(
|
||||
body: ConnectionTestListSchemasReq,
|
||||
customer_name: string,
|
||||
): Promise<ConnectionTestListSchemasRes> {
|
||||
const { connection_id, plugin } = body;
|
||||
return lastValueFrom(
|
||||
this.connectionTestReadClient.ListSchemas({
|
||||
connection_id,
|
||||
customer_name,
|
||||
plugin,
|
||||
}),
|
||||
);
|
||||
}
|
||||
async connectionTestListTables(
|
||||
body: ConnectionTestListTablesReq,
|
||||
customer_name: string,
|
||||
): Promise<ConnectionTestListTablesRes> {
|
||||
const { connection_id, plugin, schema } = body;
|
||||
return lastValueFrom(
|
||||
this.connectionTestReadClient.ListTables({
|
||||
connection_id,
|
||||
customer_name,
|
||||
plugin,
|
||||
schema,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async getTableMetadata(
|
||||
body: GetTableMetadataReq,
|
||||
customer_name: string,
|
||||
): Promise<GetTableMetadataRes> {
|
||||
const { schema, plugin, table_list, connection_id } = body;
|
||||
return lastValueFrom(
|
||||
this.connectionTestReadClient.GetTableMetadata({
|
||||
connection_id,
|
||||
customer_name,
|
||||
plugin,
|
||||
schema,
|
||||
table_list,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional } from 'class-validator';
|
||||
import { DatabaseConnectionPropertiesDto } from 'src/modules/connection/dtos/connection';
|
||||
import { CreateConnectionDto } from 'src/modules/connection/dtos/connection';
|
||||
export class ColumnDto {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
}
|
||||
export class TableMetadataDto {
|
||||
@ApiProperty()
|
||||
table_name: string;
|
||||
@ApiProperty({ type: [ColumnDto] })
|
||||
columns: ColumnDto[];
|
||||
@ApiProperty({ type: [ColumnDto] })
|
||||
references: ColumnDto[];
|
||||
}
|
||||
|
||||
export class ConnectionTestPingReq {
|
||||
@ApiPropertyOptional()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
plugin: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
host: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
port: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
network_config_id?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
connection_id: string;
|
||||
}
|
||||
|
||||
export class ConnectionTestPingRes {
|
||||
@ApiProperty()
|
||||
operation_result: boolean;
|
||||
}
|
||||
|
||||
export class ConnectionTestCredentialsProperties extends OmitType(
|
||||
DatabaseConnectionPropertiesDto,
|
||||
['plugin', 'credentials_type'],
|
||||
) {}
|
||||
export class ConnectionTestCredentialsReq extends OmitType(
|
||||
CreateConnectionDto,
|
||||
['properties'],
|
||||
) {
|
||||
@ApiProperty()
|
||||
properties: ConnectionTestCredentialsProperties;
|
||||
@ApiPropertyOptional()
|
||||
connection_id?: string;
|
||||
}
|
||||
|
||||
export class ConnectionTestCredentialsRes {
|
||||
@ApiProperty()
|
||||
operation_result: boolean;
|
||||
@ApiProperty()
|
||||
connection_id: string;
|
||||
}
|
||||
|
||||
export class ConnectionTestListSchemasReq {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
plugin: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
connection_id: string;
|
||||
}
|
||||
|
||||
export class ConnectionTestListSchemasRes {
|
||||
@ApiProperty()
|
||||
operation_result: boolean;
|
||||
@ApiProperty()
|
||||
schema_list: string[];
|
||||
}
|
||||
|
||||
export class ConnectionTestListTablesReq {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
plugin: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
connection_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
schema: string;
|
||||
}
|
||||
|
||||
export class ConnectionTestListTablesRes {
|
||||
@ApiProperty()
|
||||
operation_result: boolean;
|
||||
@ApiProperty()
|
||||
table_list: string[];
|
||||
}
|
||||
|
||||
export class GetTableMetadataReq {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
connection_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
plugin: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
schema: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString({ each: true })
|
||||
table_list: string[];
|
||||
}
|
||||
|
||||
export class GetTableMetadataRes {
|
||||
@ApiProperty()
|
||||
operation_result: boolean;
|
||||
@ApiProperty({ type: [TableMetadataDto] })
|
||||
tables_metadata: TableMetadataDto[];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
ClientsProviderAsyncOptions,
|
||||
GrpcOptions,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
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 {
|
||||
public name = 'ConnectionClientConfiguration';
|
||||
private config: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [
|
||||
ConnectionManager.ProtoPackages.WritePackage,
|
||||
ConnectionManager.ProtoPackages.ReadPackage,
|
||||
],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [
|
||||
ConnectionManager.ProtoPaths.WriteFilePath,
|
||||
ConnectionManager.ProtoPaths.ReadFilePath,
|
||||
],
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
providerOptions: ClientsProviderAsyncOptions = {
|
||||
name: this.name,
|
||||
...this.config,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
import { OnModuleInit, Inject } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { ConnectionManager } from '@dadosfera/protospack-v2';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
ConnectionApiConnection,
|
||||
ConnectionRes,
|
||||
ConnectionsRes,
|
||||
ConnectionToCatalogDto,
|
||||
CreateConnectionDto,
|
||||
UpdateConnectionDto,
|
||||
} from './dtos/connection';
|
||||
import { ConnectionClientConfiguration } from './client.config';
|
||||
import {
|
||||
GetConnectionDetailsResponse,
|
||||
INTERNAL_GetAllConnectionsResponse,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/messages';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { ConnectorClientService } from '../connector/client.service';
|
||||
import {
|
||||
ConnectionsApiNetworkConfig,
|
||||
NetworkConfigDto,
|
||||
} from '../network-config/dto/network-config';
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
||||
|
||||
export class ConnectionClientService implements OnModuleInit {
|
||||
private connectionServiceRead: ConnectionManager.ReadService.ConnectionManagerReadServices;
|
||||
private connectionServiceWrite: ConnectionManager.WriteService.ConnectionManagerWriteServices;
|
||||
logger: DadosferaLogger;
|
||||
|
||||
constructor(
|
||||
@Inject(ConnectionClientConfiguration.name)
|
||||
private readonly grpcClient: ClientGrpc,
|
||||
private usersService: UsersService,
|
||||
private connectorService: ConnectorClientService,
|
||||
private dadosferaLogger: DadosferaLogger,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.connectionServiceWrite =
|
||||
this.grpcClient.getService<ConnectionManager.WriteService.ConnectionManagerWriteServices>(
|
||||
ConnectionManager.ProtoServices.ConnectionManagerWriteServices,
|
||||
);
|
||||
|
||||
this.connectionServiceRead =
|
||||
this.grpcClient.getService<ConnectionManager.ReadService.ConnectionManagerReadServices>(
|
||||
ConnectionManager.ProtoServices.ConnectionManagerReadServices,
|
||||
);
|
||||
}
|
||||
|
||||
async createConnection(
|
||||
body: CreateConnectionDto,
|
||||
metadata,
|
||||
): Promise<ConnectionRes> {
|
||||
const {
|
||||
connector_id,
|
||||
description,
|
||||
image_url,
|
||||
name,
|
||||
network_config_id,
|
||||
properties,
|
||||
type,
|
||||
connector_name,
|
||||
plugin,
|
||||
} = body;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionServiceWrite.CreateConnection(
|
||||
{
|
||||
connector_id,
|
||||
connector_name,
|
||||
description,
|
||||
image_url,
|
||||
name,
|
||||
network_config_id,
|
||||
properties: JSON.stringify(properties),
|
||||
type,
|
||||
plugin,
|
||||
},
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async updateConnection(
|
||||
id: string,
|
||||
body: UpdateConnectionDto,
|
||||
metadata,
|
||||
): Promise<ConnectionRes> {
|
||||
const { description, name, network_config_id, properties } = body;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionServiceWrite.UpdateConnection(
|
||||
{
|
||||
id,
|
||||
description,
|
||||
name,
|
||||
network_config_id,
|
||||
properties: JSON.stringify(properties),
|
||||
},
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async deleteConnection({ body, metadata }): Promise<ConnectionRes> {
|
||||
const { id } = body;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionServiceWrite.DeleteConnection(
|
||||
{
|
||||
id,
|
||||
},
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getAllConnections({ body, metadata }): Promise<ConnectionsRes> {
|
||||
const { search, filters, size, page } = body;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionServiceRead.GetAllConnection(
|
||||
{ search, filters: JSON.stringify(filters), size, page },
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getConnectionDetails({
|
||||
body,
|
||||
metadata,
|
||||
}): Promise<GetConnectionDetailsResponse> {
|
||||
const id = body.connection_id || body.id;
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectionServiceRead.GetConnectionDetails({ id }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
async INTERNAL_getAllConnections({
|
||||
customer_name,
|
||||
}: {
|
||||
customer_name: string;
|
||||
}): Promise<INTERNAL_GetAllConnectionsResponse> {
|
||||
return lastValueFrom(
|
||||
this.connectionServiceRead.INTERNAL_GetAllConnections({ customer_name }),
|
||||
);
|
||||
}
|
||||
|
||||
async catalogExistingConnection(data: {
|
||||
connection?: ConnectionToCatalogDto;
|
||||
networkConfig?: NetworkConfigDto;
|
||||
}) {
|
||||
return lastValueFrom(
|
||||
this.connectionServiceRead.INTERNAL_CatalogExistingConnection({
|
||||
data_str: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async synchronizeConnections(apply: boolean) {
|
||||
const connectors = await this.connectorService
|
||||
.getAllConnectors({
|
||||
search: undefined,
|
||||
filters: undefined,
|
||||
language: 'pt-br',
|
||||
page: 1,
|
||||
size: 500,
|
||||
})
|
||||
.then(({ connectors: connectorsStr }) =>
|
||||
JSON.parse(connectorsStr).map((c) => c),
|
||||
);
|
||||
const { customers } = await this.usersService.findAllCustomers();
|
||||
|
||||
const customer_connections: {
|
||||
customer_name: string;
|
||||
customer_id: string;
|
||||
productConnections: Record<string, any>[];
|
||||
platformConnections: ConnectionApiConnection[];
|
||||
productNetworkConfigs: Record<string, any>[];
|
||||
platformNetworkConfigs: ConnectionsApiNetworkConfig[];
|
||||
}[] = [];
|
||||
for (const customer of customers) {
|
||||
const { name: customer_name, id: customer_id } = customer;
|
||||
|
||||
const customer_connection = await this.INTERNAL_getAllConnections({
|
||||
customer_name,
|
||||
}).then((res) => ({
|
||||
customer_name,
|
||||
customer_id,
|
||||
platformConnections: res.platformConnections
|
||||
? JSON.parse(res.platformConnections)
|
||||
: [],
|
||||
productConnections: res.productConnections
|
||||
? JSON.parse(res.productConnections)
|
||||
: [],
|
||||
productNetworkConfigs: res.productNetworkConfigs
|
||||
? JSON.parse(res.productNetworkConfigs)
|
||||
: [],
|
||||
platformNetworkConfigs: res.platformNetworkConfigs
|
||||
? JSON.parse(res.platformNetworkConfigs)
|
||||
: [],
|
||||
}));
|
||||
customer_connections.push(customer_connection);
|
||||
}
|
||||
this.logger.info(
|
||||
'DEBUG_ConnectionsMigrations - ' + JSON.stringify(customer_connections),
|
||||
);
|
||||
|
||||
const migrations: {
|
||||
customer_name: string;
|
||||
status: any[];
|
||||
productConnectionsMissing: ConnectionApiConnection[];
|
||||
productNetworkConfigsMissing: ConnectionsApiNetworkConfig[];
|
||||
}[] = [];
|
||||
for (const customer_connection of customer_connections) {
|
||||
const productConnectionsMissing: ConnectionApiConnection[] = [];
|
||||
const productNetworkConfigsMissing: ConnectionsApiNetworkConfig[] = [];
|
||||
const status: any[] = [];
|
||||
let updatePromises: Promise<any>[] = [];
|
||||
|
||||
this.logger.info(
|
||||
'DEBUG_ConnectionsMigrations - customer_connections ' +
|
||||
JSON.stringify(customer_connection),
|
||||
);
|
||||
const {
|
||||
customer_name,
|
||||
productConnections,
|
||||
platformConnections,
|
||||
productNetworkConfigs,
|
||||
platformNetworkConfigs,
|
||||
customer_id,
|
||||
} = customer_connection;
|
||||
|
||||
platformConnections?.forEach((plc) => {
|
||||
const productConnection = productConnections.find(
|
||||
(prc) => prc.id === plc.config_id,
|
||||
);
|
||||
if (!productConnection) productConnectionsMissing.push(plc);
|
||||
});
|
||||
platformNetworkConfigs?.forEach((platNetConfig) => {
|
||||
const productNetworkConfig = productNetworkConfigs.find(
|
||||
(prodNetConfig) => prodNetConfig.id === platNetConfig.config_id,
|
||||
);
|
||||
if (!productNetworkConfig)
|
||||
productNetworkConfigsMissing.push(platNetConfig);
|
||||
});
|
||||
if (apply) {
|
||||
for (const network_config of productNetworkConfigsMissing) {
|
||||
const networkConfigToCatalog = {
|
||||
id: network_config.config_id,
|
||||
created_at: network_config.created_at,
|
||||
updated_at: network_config.updated_at,
|
||||
customer_name: customer_name,
|
||||
customer_id: customer_id,
|
||||
user_id: '',
|
||||
network_type_id: '',
|
||||
network_type_name: network_config.type,
|
||||
type: network_config.type,
|
||||
plugin: network_config.plugin,
|
||||
name: network_config.name,
|
||||
description: network_config.description,
|
||||
properties: undefined,
|
||||
keywords: undefined,
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
updatePromises.push(
|
||||
this.catalogExistingConnection({
|
||||
networkConfig: networkConfigToCatalog,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
'DEBUG_ConnectionsMigrations - Creating missing network configs for customer' +
|
||||
customer_name +
|
||||
' - ' +
|
||||
JSON.stringify(customer_connection),
|
||||
);
|
||||
const updateStatuses = await Promise.all(updatePromises);
|
||||
status.push(...updateStatuses);
|
||||
updatePromises = [];
|
||||
for (const connection of productConnectionsMissing) {
|
||||
const connector = connectors.find(
|
||||
(c) => c._source.plugin === connection.plugin,
|
||||
);
|
||||
const connectionToCatalog = {
|
||||
id: connection.config_id,
|
||||
created_at: connection.created_at,
|
||||
updated_at: new Date().toISOString(),
|
||||
customer_id: customer_id,
|
||||
user_id: '',
|
||||
plugin: connection.plugin,
|
||||
name: connection.name,
|
||||
description: connection.description,
|
||||
image_url: connector._source.image,
|
||||
network_config_id: connection.network_config_id,
|
||||
type: connection.type,
|
||||
connector_id: connector._id,
|
||||
connector_name: connector._source.name,
|
||||
in_use: 1,
|
||||
username: '',
|
||||
customer_name,
|
||||
};
|
||||
|
||||
updatePromises.push(
|
||||
this.catalogExistingConnection({ connection: connectionToCatalog }),
|
||||
);
|
||||
}
|
||||
this.logger.info(
|
||||
'DEBUG_ConnectionsMigrations - Creating missing connections for customer' +
|
||||
' - ' +
|
||||
customer_name +
|
||||
JSON.stringify(customer_connection),
|
||||
);
|
||||
const updateStatuses_2 = await Promise.all(updatePromises);
|
||||
status.push(...updateStatuses_2);
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
'DEBUG_ConnectionsMigrations - migrations for customer ' +
|
||||
customer_name +
|
||||
' - ' +
|
||||
JSON.stringify(customer_connection),
|
||||
);
|
||||
migrations.push({
|
||||
customer_name,
|
||||
status,
|
||||
productConnectionsMissing,
|
||||
productNetworkConfigsMissing,
|
||||
});
|
||||
}
|
||||
|
||||
return { migrations };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Post,
|
||||
Inject,
|
||||
Put,
|
||||
Param,
|
||||
Delete,
|
||||
Get,
|
||||
Query,
|
||||
UseFilters,
|
||||
} from '@nestjs/common';
|
||||
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
||||
import { ConnectionClientService } from './client.service';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import {
|
||||
Authenticated,
|
||||
RequireAllPermissions,
|
||||
} from 'src/decorators/authentication.decorator';
|
||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||
import { ValidationPipe } from '../../pipes/object-validation.pipe';
|
||||
import {
|
||||
ConnectionDetailsRes,
|
||||
ConnectionRes,
|
||||
ConnectionsRes,
|
||||
GetAllConnectionsReq,
|
||||
UpdateConnectionDto,
|
||||
} from './dtos/connection';
|
||||
import { CreateConnectionDto } from './dtos/connection';
|
||||
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;
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
@ApiTags('connections')
|
||||
@Authenticated()
|
||||
@Controller('connections')
|
||||
export class ConnectionController {
|
||||
logger: any;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private clientService: ConnectionClientService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Get('connections-migration')
|
||||
@ApiExcludeEndpoint()
|
||||
@RequireAllPermissions(
|
||||
PERMISSIONS_GROUPS.DADOSFERA.permissions.CONNECTIONS_MIGRATION,
|
||||
)
|
||||
async synchronizeConnections(@Query('apply') apply: string) {
|
||||
return this.clientService.synchronizeConnections(apply === 'true');
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequireAllPermissions(connectionPermissions.CREATE)
|
||||
async createConnection(
|
||||
@User() user: RequestUser,
|
||||
@Body(new ValidationPipe()) body: CreateConnectionDto,
|
||||
): Promise<ConnectionRes> {
|
||||
this.logger.info('/connections - Create Connection');
|
||||
|
||||
const { user_id, customer_id, customer_name, username } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
user_id,
|
||||
customer_id,
|
||||
customer_name,
|
||||
username,
|
||||
});
|
||||
|
||||
const response: any = await this.clientService.createConnection(
|
||||
body,
|
||||
metadata,
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Put('/:id')
|
||||
@RequireAllPermissions(connectionPermissions.CREATE)
|
||||
async updateConnection(
|
||||
@User() user: RequestUser,
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('id') id: string,
|
||||
@Body(new ValidationPipe()) body: UpdateConnectionDto,
|
||||
): Promise<ConnectionRes> {
|
||||
this.logger.info('/connections - Update Connection');
|
||||
|
||||
const { user_id, customer_id, customer_name, username } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
user_id,
|
||||
customer_id,
|
||||
customer_name,
|
||||
language,
|
||||
username,
|
||||
});
|
||||
|
||||
const response: any = await this.clientService.updateConnection(
|
||||
id,
|
||||
body,
|
||||
metadata,
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@RequireAllPermissions(connectionPermissions.DELETE)
|
||||
async deleteConnection(
|
||||
@User() user: RequestUser,
|
||||
@Param('id') id: string,
|
||||
): Promise<ConnectionRes> {
|
||||
this.logger.info('/connections - Delete Connection');
|
||||
|
||||
const { user_id, customer_id, customer_name, username } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
user_id,
|
||||
customer_id,
|
||||
customer_name,
|
||||
username,
|
||||
});
|
||||
|
||||
const response: any = await this.clientService.deleteConnection({
|
||||
body: { id },
|
||||
metadata,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getAllConnections(
|
||||
@User() user: RequestUser,
|
||||
@Language() language: LanguageEnum,
|
||||
@Query() queries: GetAllConnectionsReq,
|
||||
): Promise<ConnectionsRes> {
|
||||
this.logger.info('/connections - Get All Connection');
|
||||
|
||||
const { user_id, customer_id, customer_name, username } = user;
|
||||
const { search, size, page, ...filters } = queries;
|
||||
const metadata = PackTheMetadata({
|
||||
user_id,
|
||||
customer_id,
|
||||
customer_name,
|
||||
language,
|
||||
username,
|
||||
});
|
||||
|
||||
const response = await this.clientService.getAllConnections({
|
||||
body: { search, size, page, filters },
|
||||
metadata,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
async getConnectionDetails(
|
||||
@Language() language: LanguageEnum,
|
||||
@User() user: RequestUser,
|
||||
@Param('id') id: string,
|
||||
@Query() queries,
|
||||
): Promise<ConnectionDetailsRes> {
|
||||
this.logger.info('/connections - Get Connection Details');
|
||||
|
||||
console.log(queries);
|
||||
|
||||
const { details, sensitive } = queries;
|
||||
|
||||
const { user_id, customer_id, customer_name, username } = user;
|
||||
const metadata = PackTheMetadata({
|
||||
user_id,
|
||||
customer_id,
|
||||
customer_name,
|
||||
username,
|
||||
details,
|
||||
language,
|
||||
sensitive,
|
||||
});
|
||||
|
||||
const response: any = await this.clientService.getConnectionDetails({
|
||||
body: { id },
|
||||
metadata,
|
||||
});
|
||||
|
||||
if (details === 'true') {
|
||||
const properties = JSON.parse(response.connection.properties);
|
||||
|
||||
Object.assign(response.connection, {
|
||||
pipelines: JSON.parse(response.connection.pipelines),
|
||||
connection_controls: properties.connection_controls,
|
||||
config_controls: properties.config_controls,
|
||||
connection_steps: properties.connection_steps,
|
||||
});
|
||||
|
||||
delete response.connection.properties;
|
||||
} else {
|
||||
Object.assign(response.connection, {
|
||||
properties: JSON.parse(response.connection.properties),
|
||||
pipelines: JSON.parse(response.connection.pipelines),
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { ConnectionClientService } from './client.service';
|
||||
import { ConnectionClientConfiguration } from './client.config';
|
||||
import { ConnectionController } from './connection.controller';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { ConnectorModule } from '../connector/connector.module';
|
||||
|
||||
const client = new ConnectionClientConfiguration();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.register([client.providerOptions]),
|
||||
UsersModule,
|
||||
ConnectorModule,
|
||||
],
|
||||
controllers: [ConnectionController],
|
||||
providers: [ConnectionClientService, DadosferaLogger],
|
||||
exports: [ConnectionClientService],
|
||||
})
|
||||
export class ConnectionModule {}
|
||||
@@ -0,0 +1,201 @@
|
||||
import {
|
||||
ApiProperty,
|
||||
ApiPropertyOptional,
|
||||
OmitType,
|
||||
PickType,
|
||||
} from '@nestjs/swagger';
|
||||
import {
|
||||
Connection,
|
||||
ConnectionToCatalog,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/entities';
|
||||
import {
|
||||
GetAllConnectionRequest,
|
||||
GetAllConnectionResponse,
|
||||
GetConnectionDetailsResponse,
|
||||
GetConnectionResponse,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/ConnectionManager/interfaces/messages';
|
||||
export type ConnectionTypes = 'database' | 'file' | 'application';
|
||||
export type ConnectionCredentialsType =
|
||||
| 'basic_auth'
|
||||
| 'iam_user'
|
||||
| 'role_name'
|
||||
| 'oauth'
|
||||
| 'api_key'
|
||||
| 'service_account'
|
||||
| 'headers_auth';
|
||||
export interface ConnectionApiConnection {
|
||||
config_id: string;
|
||||
plugin: string;
|
||||
secret_id: string;
|
||||
created_at: string;
|
||||
network_config_id: null;
|
||||
type: ConnectionTypes;
|
||||
credentials_type: ConnectionCredentialsType;
|
||||
credentials: Record<string, any>;
|
||||
sensitive_fields: string[];
|
||||
name: string;
|
||||
customer_name: string;
|
||||
description: string;
|
||||
updated_at: string;
|
||||
}
|
||||
export class ConnectionDto implements Connection {
|
||||
// ---Automatically generated information will not be sent by the frontend--- //
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
customer_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
customer_name: string;
|
||||
|
||||
@ApiProperty()
|
||||
user_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
updated_at: string;
|
||||
|
||||
@ApiProperty()
|
||||
created_at: string;
|
||||
|
||||
@ApiProperty()
|
||||
pipelines: string;
|
||||
|
||||
// ---Information sent by the frontend--- //
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
network_config_id: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
properties: DatabaseConnectionPropertiesDto | any;
|
||||
|
||||
// ---Information sent by the frontend (Heirs of Connector)--- //
|
||||
|
||||
@ApiProperty()
|
||||
connector_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
connector_name: string;
|
||||
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
|
||||
@ApiProperty()
|
||||
image_url: string;
|
||||
}
|
||||
|
||||
export class ConnectionToCatalogDto implements ConnectionToCatalog {
|
||||
// ---Automatically generated information - will not be sent by the frontend--- //
|
||||
customer_id: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
// ---Information extracted from token--- //
|
||||
user_id: string;
|
||||
username: string;
|
||||
customer_name: string;
|
||||
|
||||
// ---Information sent by the frontend--- //
|
||||
@ApiPropertyOptional()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
network_config_id: string | null;
|
||||
|
||||
// ---Information sent by the frontend (Heirs of Connector)--- //
|
||||
@ApiProperty()
|
||||
connector_id: string;
|
||||
|
||||
@ApiProperty()
|
||||
connector_name: string;
|
||||
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
|
||||
@ApiProperty()
|
||||
image_url: string;
|
||||
}
|
||||
|
||||
export class CreateConnectionDto extends OmitType(ConnectionDto, [
|
||||
'id',
|
||||
'customer_id',
|
||||
'customer_name',
|
||||
'user_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'pipelines',
|
||||
]) {}
|
||||
|
||||
export class UpdateConnectionDto extends PickType(ConnectionDto, [
|
||||
'name',
|
||||
'description',
|
||||
'network_config_id',
|
||||
'properties',
|
||||
]) {}
|
||||
|
||||
export class GetAllConnectionsReq implements GetAllConnectionRequest {
|
||||
@ApiPropertyOptional()
|
||||
search?: string;
|
||||
@ApiPropertyOptional()
|
||||
page?: string;
|
||||
@ApiPropertyOptional()
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export class ConnectionsRes implements GetAllConnectionResponse {
|
||||
@ApiProperty({ type: [ConnectionToCatalogDto] })
|
||||
connections: ConnectionToCatalogDto[];
|
||||
|
||||
@ApiProperty()
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class ConnectionRes implements GetConnectionResponse {
|
||||
@ApiProperty()
|
||||
connection?: ConnectionToCatalogDto;
|
||||
|
||||
@ApiProperty()
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class ConnectionDetailsRes implements GetConnectionDetailsResponse {
|
||||
@ApiProperty()
|
||||
connection?: ConnectionDto;
|
||||
|
||||
@ApiProperty()
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class DatabaseConnectionPropertiesDto {
|
||||
@ApiProperty()
|
||||
credentials_type: 'basic_auth';
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
@ApiProperty()
|
||||
host: string;
|
||||
@ApiProperty()
|
||||
port: string;
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
@ApiProperty()
|
||||
database: string;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
ClientsProviderAsyncOptions,
|
||||
GrpcOptions,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
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 {
|
||||
public name = 'ConnectorClientConfiguration';
|
||||
private config: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [
|
||||
ConnectorManager.ProtoPackages.WritePackage,
|
||||
ConnectorManager.ProtoPackages.ReadPackage,
|
||||
],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [
|
||||
ConnectorManager.ProtoPaths.WriteFilePath,
|
||||
ConnectorManager.ProtoPaths.ReadFilePath,
|
||||
],
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
providerOptions: ClientsProviderAsyncOptions = {
|
||||
name: this.name,
|
||||
...this.config,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import { Metadata } from '@grpc/grpc-js';
|
||||
import {
|
||||
OnModuleInit,
|
||||
Inject,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { ConnectorManager } from '@dadosfera/protospack-v2';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ConnectorClientConfiguration } from './client.config';
|
||||
|
||||
export class ConnectorClientService implements OnModuleInit {
|
||||
private connectorServiceRead: ConnectorManager.ReadService.ConnectorManagerReadServices;
|
||||
private connectorServiceWrite: ConnectorManager.WriteService.ConnectorManagerWriteServices;
|
||||
constructor(
|
||||
@Inject(ConnectorClientConfiguration.name)
|
||||
private readonly grpcClient: ClientGrpc,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.connectorServiceWrite =
|
||||
this.grpcClient.getService<ConnectorManager.WriteService.ConnectorManagerWriteServices>(
|
||||
ConnectorManager.ProtoServices.ConnectorManagerWriteServices,
|
||||
);
|
||||
|
||||
this.connectorServiceRead =
|
||||
this.grpcClient.getService<ConnectorManager.ReadService.ConnectorManagerReadServices>(
|
||||
ConnectorManager.ProtoServices.ConnectorManagerReadServices,
|
||||
);
|
||||
}
|
||||
|
||||
async uploadConnector(uploadConnector) {
|
||||
const connector: ConnectorManager.Entities.ConnectorCreateRequest = {
|
||||
file: {
|
||||
buffer: uploadConnector.file.buffer,
|
||||
mimetypes: uploadConnector.file.mimetype,
|
||||
},
|
||||
connector: JSON.stringify(uploadConnector.connector),
|
||||
};
|
||||
const serviceBody: ConnectorManager.Messages.RegisterConnectorRequest = {
|
||||
connector,
|
||||
};
|
||||
|
||||
return lastValueFrom(
|
||||
this.connectorServiceWrite.RegisterConnector(serviceBody),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async uploadFile(uploadFile) {
|
||||
const body: ConnectorManager.Messages.UploadFileRequest = {
|
||||
file: {
|
||||
buffer: uploadFile.file.buffer,
|
||||
mimetypes: uploadFile.file.mimetype,
|
||||
},
|
||||
name: uploadFile.name,
|
||||
};
|
||||
|
||||
return lastValueFrom(this.connectorServiceWrite.UploadFile(body)).catch(
|
||||
(err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async uploadConnectorsFile(uploadFile: Express.Multer.File) {
|
||||
const res = await lastValueFrom(
|
||||
this.connectorServiceWrite.RegisterMultipleConnectorsWithoutImage({
|
||||
connectors: uploadFile.buffer,
|
||||
}),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
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) {
|
||||
const meta = new Metadata();
|
||||
meta.add('Language', body.language);
|
||||
return lastValueFrom(
|
||||
this.connectorServiceRead.GetAllConnectors(
|
||||
{
|
||||
search: body.search,
|
||||
filters: JSON.stringify(body.filters),
|
||||
language: body.language,
|
||||
page: body.page,
|
||||
size: body.size,
|
||||
},
|
||||
meta,
|
||||
),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async getConnectorDetails(connectorId: string, language: string) {
|
||||
const meta = new Metadata();
|
||||
meta.add('Language', language);
|
||||
return lastValueFrom(
|
||||
this.connectorServiceRead.GetConnectorDetails(
|
||||
{
|
||||
plugin: connectorId,
|
||||
},
|
||||
meta,
|
||||
),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async getConnector(plugin: string, language: string) {
|
||||
const meta = new Metadata();
|
||||
meta.add('Language', language);
|
||||
return lastValueFrom(
|
||||
this.connectorServiceRead.GetConnector(
|
||||
{
|
||||
plugin,
|
||||
},
|
||||
meta,
|
||||
),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async getConnectorsTags() {
|
||||
return lastValueFrom(this.connectorServiceRead.GetConnectorsTags({})).catch(
|
||||
(err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async deleteConnector(plugin: string, version: string) {
|
||||
return lastValueFrom(
|
||||
this.connectorServiceWrite.DeleteConnector({
|
||||
version,
|
||||
plugin,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async updateConnector({ plugin, changes }) {
|
||||
return lastValueFrom(
|
||||
this.connectorServiceWrite.UpdateConnector({
|
||||
plugin,
|
||||
changes,
|
||||
}),
|
||||
).catch((err) => {
|
||||
throw new HttpException(
|
||||
err.details,
|
||||
err.code === 6 ? HttpStatus.CONFLICT : 400,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,24 +11,33 @@ import {
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
Inject,
|
||||
UploadedFiles,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { ConnectorClientService } from 'src/clients/connector/client.service';
|
||||
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import { ConnectorClientService } from './client.service';
|
||||
import { AddTagDto } from './dtos/add-tag';
|
||||
import { CreateConnectorDto } from './dtos/create-connector';
|
||||
import { DeleteConnectorDto } from './dtos/delete-connector';
|
||||
import { GetAllDto } from './dtos/get-all';
|
||||
import { RemoveTagDto } from './dtos/remove-tag';
|
||||
import { UpdateDto } from './dtos/update';
|
||||
import { UploadFileDto } from './dtos/upload-file';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import {
|
||||
Authenticated,
|
||||
RequireAllPermissions,
|
||||
RequireSomePermission,
|
||||
} from 'src/decorators/authentication.decorator';
|
||||
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')
|
||||
@ApiBearerAuth()
|
||||
@Authenticated()
|
||||
@Controller('connectors')
|
||||
export class ConnectorController {
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@@ -38,6 +47,7 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadConnector(
|
||||
@@ -54,32 +64,58 @@ export class ConnectorController {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Post('/upload')
|
||||
@Post('/uploads')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadFile(@UploadedFile() file, @Body() { name }: UploadFileDto) {
|
||||
@UseInterceptors(FilesInterceptor('files'))
|
||||
async uploadFile(@UploadedFiles() files) {
|
||||
this.logger.info('/upload - Upload Connector Route');
|
||||
|
||||
const response = await this.connectorClientService.uploadFile({
|
||||
file,
|
||||
name,
|
||||
const promises = files.map(async (file) => {
|
||||
return await this.connectorClientService.uploadFile({
|
||||
file,
|
||||
name: file.originalname.split('.')[0],
|
||||
});
|
||||
});
|
||||
|
||||
const response = await Promise.all(promises);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Get()
|
||||
async getAllConnectors(@Query() queries: GetAllDto) {
|
||||
this.logger.info('/upload - Upload Connector Route');
|
||||
@Post('/multiple-connectors-upload')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.CREATE)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async uploadConnectorsFile(@UploadedFile() file: Express.Multer.File) {
|
||||
this.logger.info(`/upload`, 'Upload Connector Route');
|
||||
|
||||
const { search, ...filters } = queries;
|
||||
return await this.connectorClientService.uploadConnectorsFile(file);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||
)
|
||||
async getAllConnectors(
|
||||
@Language() language: LanguageEnum,
|
||||
@Query() queries: GetAllDto,
|
||||
) {
|
||||
this.logger.info('/GET - getAllConnectors Route');
|
||||
|
||||
const { search, size, page, ...filters } = queries;
|
||||
|
||||
const response: any = await this.connectorClientService.getAllConnectors({
|
||||
filters: filters || {},
|
||||
search: search || '',
|
||||
language: language || 'pt-br',
|
||||
size: size || 10,
|
||||
page: page || 1,
|
||||
});
|
||||
|
||||
const connectors = JSON.parse(response.connectors).connectors;
|
||||
const connectors = JSON.parse(response.connectors);
|
||||
|
||||
return {
|
||||
message: response.message,
|
||||
@@ -87,17 +123,38 @@ export class ConnectorController {
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/tags')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||
)
|
||||
async getConnectorsTags() {
|
||||
return await this.connectorClientService.getConnectorsTags();
|
||||
}
|
||||
|
||||
@Get('/:plugin')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||
)
|
||||
async getConnector(
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('plugin') plugin: string,
|
||||
@Query('version') version: string,
|
||||
) {
|
||||
this.logger.info('/upload - Upload Connector Route');
|
||||
this.logger.info('/GET/:plugin - getConnector Route');
|
||||
|
||||
const pluginId = `${plugin}-${version}`;
|
||||
|
||||
const response: any = await this.connectorClientService.getConnector(
|
||||
pluginId,
|
||||
language,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -107,16 +164,24 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Get('/:plugin/details')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.CONNECTORS.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.GET,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE,
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE,
|
||||
)
|
||||
async getConnectorDetails(
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('plugin') plugin: string,
|
||||
@Query('version') version: string,
|
||||
) {
|
||||
this.logger.info('/upload - Upload Connector Route');
|
||||
|
||||
const pluginId = `${plugin}-${version}`;
|
||||
|
||||
const response: any = await this.connectorClientService.getConnectorDetails(
|
||||
pluginId,
|
||||
language || 'pt-br',
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -126,6 +191,7 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Put('/:plugin')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
async updateConnector(
|
||||
@Param('plugin') plugin: string,
|
||||
@@ -147,6 +213,7 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Put('/:plugin/add-tag')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
|
||||
async addTagOnConnector(
|
||||
@Param('plugin') plugin: string,
|
||||
@Body() body: AddTagDto,
|
||||
@@ -173,6 +240,7 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Put('/:plugin/remove-tag')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.UPDATE)
|
||||
async removeTagOnConnector(
|
||||
@Param('plugin') plugin: string,
|
||||
@Body() body: RemoveTagDto,
|
||||
@@ -200,20 +268,18 @@ export class ConnectorController {
|
||||
}
|
||||
|
||||
@Delete('/:plugin')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.CONNECTORS.permissions.DELETE)
|
||||
async deleteConnector(
|
||||
@Param('plugin') plugin: string,
|
||||
@Body() { version }: DeleteConnectorDto,
|
||||
@Query('version') version: string,
|
||||
) {
|
||||
this.logger.info('/upload - Upload Connector Route');
|
||||
this.logger.info('DELETE /:plugin - Delete Connector Route');
|
||||
|
||||
const response: any = await this.connectorClientService.deleteConnector(
|
||||
const response = await this.connectorClientService.deleteConnector(
|
||||
plugin,
|
||||
version,
|
||||
);
|
||||
|
||||
return {
|
||||
message: response.message || 'ok',
|
||||
connector: { ...JSON.parse(response.connector) },
|
||||
};
|
||||
return { message: response.message, connector: response.connector };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { ConnectorClientService } from './client.service';
|
||||
import { ConnectorClientConfiguration } from './client.config';
|
||||
import { ConnectorController } from './connector.controller';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
const client = new ConnectorClientConfiguration();
|
||||
|
||||
@Module({
|
||||
controllers: [ConnectorController],
|
||||
providers: [ConnectorClientService, DadosferaLogger],
|
||||
imports: [ClientsModule.register([client.providerOptions])],
|
||||
exports: [ConnectorClientService],
|
||||
})
|
||||
export class ConnectorModule {}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UploadConnectorsFileDto {
|
||||
@ApiProperty({ format: 'binary' })
|
||||
file: string;
|
||||
}
|
||||
@@ -4,6 +4,15 @@ export class GetAllDto {
|
||||
@ApiProperty()
|
||||
search: string;
|
||||
|
||||
@ApiProperty()
|
||||
language: string;
|
||||
|
||||
@ApiProperty()
|
||||
size: string;
|
||||
|
||||
@ApiProperty()
|
||||
page: string;
|
||||
|
||||
@ApiProperty()
|
||||
filters: any;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
ClientsProviderAsyncOptions,
|
||||
GrpcOptions,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
import {
|
||||
ProtoPackages,
|
||||
ProtoPaths,
|
||||
} 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 {
|
||||
public name = 'DucClient';
|
||||
|
||||
private config: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.DUC_URL,
|
||||
package: [ProtoPackages.WritePackage, ProtoPackages.ReadPackage],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [ProtoPaths.WriteFilePath, ProtoPaths.ReadFilePath],
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
maxSendMessageLength: 15 * 1024 * 1024, // 15 MB por mensagem
|
||||
maxReceiveMessageLength: 15 * 1024 * 1024,
|
||||
},
|
||||
};
|
||||
|
||||
providerOptions: ClientsProviderAsyncOptions = {
|
||||
name: this.name,
|
||||
...this.config,
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Controller, Get, Inject } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { HealthService } from './health.service';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
@ApiTags('Health')
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { HealthController } from './health.controller';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
providers: [HealthService, DadosferaLogger],
|
||||
})
|
||||
export class HealthModule {}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
@Injectable()
|
||||
export class HealthService {
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
|
||||
@@ -1,135 +1,45 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Info } from 'src/clients/inputs/interfaces';
|
||||
import { Method } from 'axios';
|
||||
import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger';
|
||||
|
||||
export class InputModel {
|
||||
category: string;
|
||||
plugin: string;
|
||||
name: string;
|
||||
cron?: string;
|
||||
credentials?: {
|
||||
connection_type: string;
|
||||
oauth: string;
|
||||
oauth_code: string;
|
||||
start_date: string;
|
||||
};
|
||||
options?: {
|
||||
oauth?: {
|
||||
get_tokens_url: string;
|
||||
get_tokens_method?: Method;
|
||||
get_tokens_url_params: string;
|
||||
get_tokens_set_response: Record<string, string>;
|
||||
content_type: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class GoogleAnalyticsClientSecrets {
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
@ApiProperty()
|
||||
project_id: string;
|
||||
@ApiProperty()
|
||||
private_key_id: string;
|
||||
@ApiProperty()
|
||||
private_key: string;
|
||||
@ApiProperty()
|
||||
client_email: string;
|
||||
@ApiProperty()
|
||||
client_id: string;
|
||||
@ApiProperty()
|
||||
auth_uri: string;
|
||||
@ApiProperty()
|
||||
token_uri: string;
|
||||
@ApiProperty()
|
||||
auth_provider_x509_cert_url: string;
|
||||
@ApiProperty()
|
||||
client_x509_cert_url: string;
|
||||
}
|
||||
class OauthObject {
|
||||
@ApiPropertyOptional()
|
||||
get_tokens_url: string;
|
||||
@ApiPropertyOptional()
|
||||
get_tokens_url_params: string;
|
||||
@ApiPropertyOptional()
|
||||
get_tokens_set_response: Record<string, any>;
|
||||
@ApiPropertyOptional()
|
||||
content_type: string;
|
||||
}
|
||||
class InputOptions {
|
||||
@ApiPropertyOptional()
|
||||
oauth: OauthObject;
|
||||
@ApiPropertyOptional()
|
||||
skip_select_columns: true;
|
||||
@ApiPropertyOptional()
|
||||
skip_select_entities: false;
|
||||
@ApiPropertyOptional()
|
||||
skip_transformation: true;
|
||||
}
|
||||
|
||||
class CredentialsJdbc {
|
||||
@ApiPropertyOptional()
|
||||
jdbc_user: string;
|
||||
@ApiPropertyOptional()
|
||||
jdbc_password: string;
|
||||
@ApiPropertyOptional()
|
||||
database: string;
|
||||
@ApiPropertyOptional()
|
||||
endpoint: string;
|
||||
@ApiPropertyOptional()
|
||||
port: string;
|
||||
@ApiPropertyOptional()
|
||||
engine: string;
|
||||
@ApiPropertyOptional()
|
||||
schema: string;
|
||||
}
|
||||
class FileFormatParams {
|
||||
@ApiPropertyOptional()
|
||||
file_format: string;
|
||||
@ApiPropertyOptional()
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
class Credentials extends CredentialsJdbc {
|
||||
@ApiProperty()
|
||||
connection_type: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
client_aws_access_key_id: string;
|
||||
@ApiPropertyOptional()
|
||||
client_aws_secret_access_key: string;
|
||||
@ApiPropertyOptional()
|
||||
client_bucket: string;
|
||||
@ApiPropertyOptional()
|
||||
file_to_extract: string;
|
||||
@ApiPropertyOptional()
|
||||
file_format_params: FileFormatParams;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
view_id: string;
|
||||
@ApiPropertyOptional()
|
||||
client_secrets: GoogleAnalyticsClientSecrets;
|
||||
@ApiPropertyOptional()
|
||||
start_date: string;
|
||||
@ApiPropertyOptional()
|
||||
end_date: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
oauth_code: string;
|
||||
}
|
||||
class Column {
|
||||
export class Column {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
}
|
||||
class TableColumns {
|
||||
export class TableColumns {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
columns: Column[];
|
||||
columns: string[];
|
||||
@ApiProperty()
|
||||
references: Column[];
|
||||
@ApiProperty()
|
||||
type: string;
|
||||
}
|
||||
export class AvailableEntity {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
replication_methods: string[];
|
||||
}
|
||||
|
||||
export class Input {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
@ApiProperty({ type: [TableColumns] })
|
||||
tables: TableColumns[];
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
@ApiPropertyOptional()
|
||||
type?: string;
|
||||
@ApiProperty()
|
||||
created_at: string;
|
||||
@ApiProperty()
|
||||
updated_at: string;
|
||||
}
|
||||
export class GetAvailableEntitiesReq {
|
||||
@ApiProperty()
|
||||
@@ -137,79 +47,12 @@ export class GetAvailableEntitiesReq {
|
||||
}
|
||||
|
||||
export class GetAvailableEntitiesRes {
|
||||
@ApiProperty()
|
||||
entities: string[];
|
||||
@ApiProperty({ type: [AvailableEntity] })
|
||||
entities: AvailableEntity[];
|
||||
}
|
||||
|
||||
export class TestConnectionGetColumnsReq {
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
@ApiProperty()
|
||||
tables: string[];
|
||||
@ApiPropertyOptional()
|
||||
credentials: CredentialsJdbc;
|
||||
@ApiPropertyOptional()
|
||||
id: string;
|
||||
@ApiPropertyOptional()
|
||||
info: Info;
|
||||
}
|
||||
export class TestConnectionGetColumnsRes {
|
||||
@ApiProperty()
|
||||
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 {
|
||||
@ApiPropertyOptional()
|
||||
id: string;
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
@ApiProperty()
|
||||
category: string;
|
||||
@ApiProperty()
|
||||
credentials: Credentials;
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiPropertyOptional()
|
||||
options: InputOptions;
|
||||
@ApiPropertyOptional()
|
||||
info: Info;
|
||||
}
|
||||
|
||||
export class Input {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
@ApiProperty()
|
||||
category: string;
|
||||
@ApiProperty()
|
||||
plugin: string;
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
cron: string;
|
||||
@ApiProperty()
|
||||
credentials: Credentials;
|
||||
@ApiProperty()
|
||||
client_id: string;
|
||||
@ApiProperty()
|
||||
created_at: string;
|
||||
@ApiProperty()
|
||||
updated_at: string;
|
||||
@ApiPropertyOptional()
|
||||
info: Info;
|
||||
}
|
||||
export class CreateInputReq extends OmitType(Input, [
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]) {}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Info } from '@dadosfera/protospack/dist/lib/interfaces';
|
||||
|
||||
interface Values {
|
||||
jdbc_user: string;
|
||||
jdbc_password: string;
|
||||
@@ -9,32 +11,6 @@ interface Values {
|
||||
schema: string;
|
||||
}
|
||||
|
||||
interface Cron {
|
||||
hours: string[];
|
||||
hour_interval: boolean;
|
||||
hour_resourse: boolean;
|
||||
hour_resourse_value: number;
|
||||
month_day: string[];
|
||||
month_day_interval: boolean;
|
||||
month_day_resourse: boolean;
|
||||
month_day_resourse_value: number;
|
||||
month: string[];
|
||||
month_interval: boolean;
|
||||
month_resourse: boolean;
|
||||
month_resourse_value: number;
|
||||
week_days: string[];
|
||||
week_days_interval: boolean;
|
||||
week_days_resourse: boolean;
|
||||
week_days_resourse_value: number;
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
customer_id: string;
|
||||
user_id: string;
|
||||
customer: string;
|
||||
customer_tier: string;
|
||||
}
|
||||
|
||||
export interface ICreateInputRequest {
|
||||
cron: string;
|
||||
name: string;
|
||||
@@ -56,6 +32,7 @@ export interface UpdateInputRequest {
|
||||
values: Values;
|
||||
operation: string;
|
||||
info: Info;
|
||||
tables: any[];
|
||||
}
|
||||
|
||||
export interface ITestConnectionRequest {
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Input } from '@dadosfera/protospack-v2';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
ClientProviderOptions,
|
||||
Transport,
|
||||
type GrpcOptions,
|
||||
} from '@nestjs/microservices';
|
||||
|
||||
const isLocalConnection =
|
||||
process.env.INFACTORY_URL.startsWith('in-factory:') ||
|
||||
process.env.INFACTORY_URL.includes('0.0.0.0');
|
||||
export class InputsGrpcClient {
|
||||
public readonly name = 'InputsGrpcClient';
|
||||
private config: GrpcOptions = {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [
|
||||
Input.ProtoPackages.WritePackage,
|
||||
Input.ProtoPackages.ReadPackage,
|
||||
],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [
|
||||
Input.ProtoPaths.WriteFilePath,
|
||||
Input.ProtoPaths.ReadFilePath,
|
||||
],
|
||||
loader: {
|
||||
keepCase: true,
|
||||
enums: String,
|
||||
defaults: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
providerOptions: ClientProviderOptions = {
|
||||
name: this.name,
|
||||
...this.config,
|
||||
};
|
||||
}
|
||||
@@ -7,147 +7,123 @@ import {
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
UseFilters,
|
||||
} from '@nestjs/common';
|
||||
import { InputsService } from './inputs.service';
|
||||
import { DadosferaLogger } from 'dadosfera-logs';
|
||||
import { InputsClientService } from 'src/clients/inputs/client.service';
|
||||
import { UpdateInputRequest } from 'src/clients/inputs/interfaces';
|
||||
import { Permissions } from '../../authentication/permissions.enum';
|
||||
import { AuthenticateCondition } from 'src/authentication/authentication.decorator';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum';
|
||||
import { AuthenticateCondition } from 'src/decorators/authentication.decorator';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
CreateInputReq,
|
||||
GetAvailableEntitiesReq,
|
||||
GetAvailableEntitiesRes,
|
||||
Input,
|
||||
TestConnectionGetColumnsReq,
|
||||
TestConnectionGetColumnsRes,
|
||||
TestConnectionReq,
|
||||
TestConnectionRes,
|
||||
} from './dtos/input.model';
|
||||
import { UpdateInputRequest } from './dtos/old_interfaces';
|
||||
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||
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')
|
||||
@AuthenticateCondition((req, user) => {
|
||||
let action: any;
|
||||
|
||||
switch (req.method) {
|
||||
case 'POST':
|
||||
action = 'CREATE';
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
action = 'UPDATE';
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
action = 'UPDATE_PARTIAL';
|
||||
break;
|
||||
return (
|
||||
user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
|
||||
) ||
|
||||
user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
|
||||
)
|
||||
);
|
||||
|
||||
default:
|
||||
action = req.method;
|
||||
return (
|
||||
user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE.seqid,
|
||||
) ||
|
||||
user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE.seqid,
|
||||
) ||
|
||||
user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.PIPELINE.permissions.GET.seqid,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
`${req.method} ${req.route.path}` ===
|
||||
'GET /inputs/available-entities/:plugin'
|
||||
) {
|
||||
action = 'GET_ENTITIES';
|
||||
}
|
||||
|
||||
return user.permissions.includes(Permissions.INPUT[action].seqid);
|
||||
})
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
export class InputsController {
|
||||
inputService: InputsService;
|
||||
logger: any;
|
||||
logger: DadosferaLogger;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private inputsClientService: InputsClientService,
|
||||
private inputService: InputsService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
this.inputService = new InputsService(this.inputsClientService);
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Get('available-entities/:plugin')
|
||||
@ApiOkResponse({ type: GetAvailableEntitiesRes })
|
||||
async getAvailableEntities(
|
||||
@Body() body,
|
||||
@Param() params: GetAvailableEntitiesReq,
|
||||
@User() user: RequestUser,
|
||||
) {
|
||||
const { info } = body;
|
||||
const { plugin } = params;
|
||||
return await this.inputService.getAvailableEntities({ info, plugin });
|
||||
}
|
||||
|
||||
@Post('/test-connection')
|
||||
@ApiOkResponse({ type: TestConnectionRes })
|
||||
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,
|
||||
const availableEntities = await this.inputService.getAvailableEntities({
|
||||
info: {
|
||||
customer: user.customer_name,
|
||||
customer_id: user.customer_id,
|
||||
user_id: user.user_id,
|
||||
},
|
||||
);
|
||||
|
||||
const response = await this.inputService.getColumns(data);
|
||||
|
||||
return response;
|
||||
plugin,
|
||||
});
|
||||
return availableEntities;
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOkResponse({ type: Input })
|
||||
async create(@Body() createInputDto: CreateInputReq) {
|
||||
async create(@Body() body: CreateInputReq, @User() user: RequestUser) {
|
||||
const info: Info = {
|
||||
user_id: user.user_id,
|
||||
customer: user.customer_name,
|
||||
customer_id: user.customer_id,
|
||||
};
|
||||
this.logger.info(`/input - ON CREATE ROUTE`, {
|
||||
user: createInputDto.info.user_id,
|
||||
customer: createInputDto.info.customer,
|
||||
user: info.user_id,
|
||||
customer: info.customer,
|
||||
});
|
||||
|
||||
const response = await this.inputService.create(createInputDto);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
@ApiOkResponse({ type: Input })
|
||||
async reCreate(@Param('id') id: string, @Body() input: CreateInputReq) {
|
||||
if (!input.id) input.id = id;
|
||||
this.logger.info(`POST /${id}`, 'ON RECREATE ROUTE');
|
||||
|
||||
const response = await this.inputService.reCreate(input);
|
||||
const response = await this.inputService.create({ body, info });
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Get()
|
||||
async findAll(@Body() body) {
|
||||
async findAll(@User() user: RequestUser) {
|
||||
const { user_id, customer_id, customer_name: customer } = user;
|
||||
this.logger.info(`/input - ON FIND ALL ROUTE`, {
|
||||
user: body.info.user_id,
|
||||
customer: body.info.customer,
|
||||
user: user.user_id,
|
||||
customer,
|
||||
});
|
||||
|
||||
const response = await this.inputService.findAll(body);
|
||||
const info: Info = {
|
||||
customer,
|
||||
customer_id,
|
||||
user_id,
|
||||
};
|
||||
const response = await this.inputService.findAll(info);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
async findOne(@Body() body, @Param() params) {
|
||||
const { id } = params;
|
||||
async findOne(@Body() body, @Param('id') id: string) {
|
||||
this.logger.info(`/input/${id} - ON FIND ONE ROUTE`, {
|
||||
user: body.info.user_id,
|
||||
customer: body.info.customer,
|
||||
@@ -158,6 +134,7 @@ export class InputsController {
|
||||
return response;
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Patch(':id')
|
||||
async update(@Body() updateInputDto: UpdateInputRequest, @Param() params) {
|
||||
const { id } = params;
|
||||
@@ -173,6 +150,7 @@ export class InputsController {
|
||||
return response;
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Delete(':id')
|
||||
async delete(@Body() data, @Param() params) {
|
||||
const { id } = params;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { InputsGrpcClient } from './inputs-client.config';
|
||||
import { InputsController } from './inputs.controller';
|
||||
import { InputsService } from './inputs.service';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
const client = new InputsGrpcClient();
|
||||
|
||||
@Module({
|
||||
controllers: [InputsController],
|
||||
providers: [InputsService, DadosferaLogger],
|
||||
imports: [ClientsModule.register([client.providerOptions])],
|
||||
exports: [InputsService],
|
||||
})
|
||||
export class InputsModule {}
|
||||
@@ -1,20 +1,131 @@
|
||||
import { Body, HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { Timeout } from '@nestjs/schedule';
|
||||
import { DecodeGrpcStruct } from 'protospack';
|
||||
import {
|
||||
ForbiddenException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import CronParser, { CronExpression } from 'cron-parser';
|
||||
import { InputsClientService } from 'src/clients/inputs/client.service';
|
||||
import { IIdRequest, Info } from 'src/clients/inputs/interfaces';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { objectCamelToSnake } from 'src/utils/CaseConverter';
|
||||
import { IIdRequest, UpdateInputRequest } from './dtos/old_interfaces';
|
||||
import { Input } from '@dadosfera/protospack-v2';
|
||||
import {
|
||||
GetAvailableEntitiesRequest,
|
||||
InputCreateGenericRequest,
|
||||
InputCreateS3Request,
|
||||
InputNewCreateRequest,
|
||||
TestConnectionRequest,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/messages';
|
||||
import { Info } from '@dadosfera/protospack-v2/dist/lib/Input/interfaces/entities';
|
||||
import { CreateInputReq } from './dtos/input.model';
|
||||
|
||||
@Injectable()
|
||||
|
||||
//TODO refactor this class to remove OLD_inputClient
|
||||
export class InputsService {
|
||||
constructor(private inputClient: InputsClientService) { }
|
||||
logger: DadosferaLogger;
|
||||
inputWriteService: Input.WriteService.InputWriteService;
|
||||
inputReadService: Input.ReadService.InputReadService;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('InputsGrpcClient') private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.inputWriteService =
|
||||
this.grpcClient.getService<Input.WriteService.InputWriteService>(
|
||||
Input.ProtoServices.InputWriteService,
|
||||
);
|
||||
this.inputReadService =
|
||||
this.grpcClient.getService<Input.ReadService.InputReadService>(
|
||||
Input.ProtoServices.InputReadService,
|
||||
);
|
||||
}
|
||||
|
||||
OLD_inputClient = {
|
||||
newCreate: async (createInputDto: InputNewCreateRequest) => {
|
||||
this.logger.info('InputClientService - Create');
|
||||
|
||||
const input = await lastValueFrom(
|
||||
this.inputWriteService.InputNewCreate(createInputDto),
|
||||
);
|
||||
objectCamelToSnake(input);
|
||||
return input;
|
||||
},
|
||||
createS3Inputs: async (createInputDto: InputCreateS3Request) => {
|
||||
this.logger.info('InputClientService - Create');
|
||||
createInputDto;
|
||||
const createInputResponse = await lastValueFrom(
|
||||
this.inputWriteService.InputCreateS3(createInputDto),
|
||||
).catch((e) => {
|
||||
throw new HttpException(e.details, 500);
|
||||
});
|
||||
this.logger.info('Done');
|
||||
objectCamelToSnake(createInputResponse);
|
||||
return createInputResponse;
|
||||
},
|
||||
update: async (updateInputDTO: UpdateInputRequest) => {
|
||||
this.logger.info('InputClientService - Update');
|
||||
const updateInputResponse = await lastValueFrom(
|
||||
this.inputWriteService.InputUpdate(updateInputDTO),
|
||||
);
|
||||
|
||||
return updateInputResponse;
|
||||
},
|
||||
testConnection: async (data: TestConnectionRequest) => {
|
||||
this.logger.info('InputClientService - TestConnection');
|
||||
const testConnectionResponse = await lastValueFrom(
|
||||
this.inputReadService.NewTestConnection(data),
|
||||
);
|
||||
|
||||
return testConnectionResponse;
|
||||
},
|
||||
getAvailableEntities: async (data) => {
|
||||
this.logger.info('InputClientService - GetAvailableEntities');
|
||||
data;
|
||||
const response = await lastValueFrom(
|
||||
this.inputReadService.GetAvailableEntities(data),
|
||||
).catch((err) => {
|
||||
this.logger.info(err.details);
|
||||
if (err.details && err.details.includes('400'))
|
||||
throw new HttpException('Plugin inválido', HttpStatus.BAD_REQUEST);
|
||||
throw new HttpException(err.details, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
objectCamelToSnake(response);
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
secondsInADay = 60 * 60 * 24;
|
||||
secondsInAnHour = 60 * 60;
|
||||
|
||||
adjustInputPayload(payload) {
|
||||
if (payload?.input_generic) return DecodeGrpcStruct(payload.input_generic);
|
||||
if (!payload.input_s3 && !payload.input_jdbc) return payload;
|
||||
return payload?.input_s3 || payload?.input_jdbc;
|
||||
if (payload) {
|
||||
if (payload.input_generic) {
|
||||
const { credentials } = payload.input_generic;
|
||||
return {
|
||||
...payload.input_generic,
|
||||
credentials: credentials && JSON.parse(credentials),
|
||||
};
|
||||
}
|
||||
if (payload.input_s3) return payload.input_s3;
|
||||
if (payload.input_jdbc) return payload.input_jdbc;
|
||||
|
||||
const { credentials } = payload;
|
||||
return {
|
||||
...payload,
|
||||
credentials: credentials && JSON.parse(credentials),
|
||||
};
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
getDifferenceInSeconds(date1: Date, date2: Date) {
|
||||
const diffInMs = Math.abs(date2.getTime() - date1.getTime());
|
||||
@@ -37,86 +148,61 @@ export class InputsService {
|
||||
const afterNextDate = interval.next().toDate();
|
||||
const secondsApart = this.getDifferenceInSeconds(nextDate, afterNextDate);
|
||||
if (customer_tier === 'BASIC' && secondsApart < this.secondsInADay) {
|
||||
throw new HttpException(
|
||||
throw new ForbiddenException(
|
||||
'Intervalo de tempo não pode ser inferior a um dia.',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
} else if (secondsApart < this.secondsInAnHour) {
|
||||
throw new HttpException(
|
||||
throw new ForbiddenException(
|
||||
'Intervalo de tempo não pode ser inferior a uma hora.',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
async create(@Body() data) {
|
||||
this.validateCron(data);
|
||||
let response;
|
||||
switch (data.plugin.toLowerCase()) {
|
||||
case 'csv':
|
||||
case 'json':
|
||||
case 'parquet':
|
||||
const { info, ...input } = data;
|
||||
const inputPayload = this.generateInputS3Payload(input);
|
||||
response = await this.inputClient.createS3Inputs({
|
||||
input: inputPayload,
|
||||
info,
|
||||
});
|
||||
break;
|
||||
case 'oracle':
|
||||
case 'mysql':
|
||||
case 'postgresql':
|
||||
case 'sqlserver':
|
||||
response = await this.inputClient.newCreate(data);
|
||||
break;
|
||||
default:
|
||||
response = await this.inputClient.createGeneric(data);
|
||||
break;
|
||||
}
|
||||
const adjustedInput = this.adjustInputPayload(response.input);
|
||||
return { ...response, input: adjustedInput };
|
||||
async create(data: { body: CreateInputReq; info: Info }) {
|
||||
// this.validateCron(data);
|
||||
const { body, info } = data;
|
||||
|
||||
const inputCreateGenericRequest: InputCreateGenericRequest = {
|
||||
input: {
|
||||
...body,
|
||||
},
|
||||
info,
|
||||
};
|
||||
|
||||
const { input } = await lastValueFrom(
|
||||
this.inputWriteService.InputCreate(inputCreateGenericRequest),
|
||||
);
|
||||
const adjustedInput = this.adjustInputPayload(input);
|
||||
return { input: adjustedInput };
|
||||
}
|
||||
async reCreate(data) {
|
||||
const { info, cron } = data;
|
||||
if (cron) this.validateCron({ info, cron });
|
||||
const response = await this.inputClient.createGeneric(data);
|
||||
const adjustedInput = this.adjustInputPayload(response.input);
|
||||
return { ...response, input: adjustedInput };
|
||||
|
||||
async getAvailableEntities(data: GetAvailableEntitiesRequest) {
|
||||
return lastValueFrom(this.inputReadService.GetAvailableEntities(data));
|
||||
}
|
||||
async getAvailableEntities(data): Promise<{ entities: string[] }> {
|
||||
return await this.inputClient.getAvailableEntities(data);
|
||||
}
|
||||
async findAll(body) {
|
||||
try {
|
||||
const findAllInputResponse: any = await this.inputClient.findAll(body);
|
||||
if (findAllInputResponse?.inputs?.length) {
|
||||
findAllInputResponse.inputs = findAllInputResponse.inputs.map((input) =>
|
||||
this.adjustInputPayload(input),
|
||||
);
|
||||
}
|
||||
return findAllInputResponse;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
async findAll(info: Info) {
|
||||
const { inputs } = await lastValueFrom(
|
||||
this.inputReadService.InputFindAll({ info }),
|
||||
);
|
||||
if (!inputs) return { inputs: [] };
|
||||
|
||||
const inputsReturn = inputs.map((i) => this.adjustInputPayload(i));
|
||||
return { inputs: inputsReturn };
|
||||
}
|
||||
|
||||
async findOne(idRequest: IIdRequest) {
|
||||
try {
|
||||
const findOneInputResponse: any = await this.inputClient.findOne(
|
||||
idRequest,
|
||||
);
|
||||
findOneInputResponse.input = this.adjustInputPayload(
|
||||
findOneInputResponse.input,
|
||||
);
|
||||
return findOneInputResponse;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const findOneInputResponse: any = await lastValueFrom(
|
||||
this.inputReadService.InputFindOne(idRequest),
|
||||
);
|
||||
findOneInputResponse.input = this.adjustInputPayload(
|
||||
findOneInputResponse.input,
|
||||
);
|
||||
return findOneInputResponse;
|
||||
}
|
||||
|
||||
async update(id: string, data, info: Info) {
|
||||
this.validateCron({ ...data, info });
|
||||
try {
|
||||
const updateInputResponse: any = await this.inputClient.update({
|
||||
const updateInputResponse: any = await this.OLD_inputClient.update({
|
||||
id,
|
||||
info,
|
||||
...data,
|
||||
@@ -132,21 +218,13 @@ export class InputsService {
|
||||
}
|
||||
|
||||
async remove(idRequest: IIdRequest) {
|
||||
try {
|
||||
const removeInputResponse = await this.inputClient.remove(idRequest);
|
||||
|
||||
return removeInputResponse;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return lastValueFrom(this.inputWriteService.InputRemove(idRequest));
|
||||
}
|
||||
|
||||
@Timeout(60000 * 10) // Timeout set for 10 minutes
|
||||
async testConnection(data) {
|
||||
try {
|
||||
const testConnectionInputResponse = await this.inputClient.testConnection(
|
||||
data,
|
||||
);
|
||||
const testConnectionInputResponse =
|
||||
await this.OLD_inputClient.testConnection(data);
|
||||
|
||||
return testConnectionInputResponse;
|
||||
} catch (err) {
|
||||
@@ -154,17 +232,6 @@ export class InputsService {
|
||||
}
|
||||
}
|
||||
|
||||
async getColumns(data) {
|
||||
try {
|
||||
const testConnectionGetColumnsResponse =
|
||||
await this.inputClient.getColumns(data);
|
||||
|
||||
return testConnectionGetColumnsResponse;
|
||||
} catch (err) {
|
||||
throw new HttpException(err.message, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
generateInputS3Payload(payload) {
|
||||
const { credentials, plugin, cron } = 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 : ''}`;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user