mirror of
https://github.com/dadosfera/maestro.git
synced 2026-09-03 21:24:49 +00:00
Compare commits
994
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d04c1d495 | ||
|
|
cd55dc0dc4 | ||
|
|
f1d56e4c2c | ||
|
|
bc49f79cb3 | ||
|
|
00163ad894 | ||
|
|
cb8798d871 | ||
|
|
be70f4da09 | ||
|
|
b436d7de7a | ||
|
|
2a3ab4228f | ||
|
|
8e605aa361 | ||
|
|
fa4267f54a | ||
|
|
47d8ca2760 | ||
|
|
f3c51e5328 | ||
|
|
dcf70fdaae | ||
|
|
70f663374d | ||
|
|
38f2317bb6 | ||
|
|
31e0ca6c91 | ||
|
|
dc063cbf3f | ||
|
|
a4c82ae4a8 | ||
|
|
11a65e11d1 | ||
|
|
73d47f0ff5 | ||
|
|
d36e532c8e | ||
|
|
b2a24e3a49 | ||
|
|
301b6e98ec | ||
|
|
381a401ebb | ||
|
|
ba53934068 | ||
|
|
1b6846532f | ||
|
|
216349f303 | ||
|
|
878c9cf450 | ||
|
|
a654baef13 | ||
|
|
df852449b2 | ||
|
|
04d761ca14 | ||
|
|
9b168612a4 | ||
|
|
a772804dcd | ||
|
|
8a399fbbcf | ||
|
|
94049d6e8e | ||
|
|
c28cc58ac7 | ||
|
|
56a655bb90 | ||
|
|
b3d4a6b028 | ||
|
|
c7bd1f761a | ||
|
|
09c045455e | ||
|
|
52d651126e | ||
|
|
2f11b1e78f | ||
|
|
885e9b71bb | ||
|
|
193031dfdd | ||
|
|
f626a9bb5e | ||
|
|
ac26ad9f44 | ||
|
|
fbe00af6dc | ||
|
|
bfa9929b69 | ||
|
|
bada7000f6 | ||
|
|
2c908d8d95 | ||
|
|
da35633d42 | ||
|
|
cc7e06013f | ||
|
|
5ec36a05b5 | ||
|
|
f61e46c7c1 | ||
|
|
b9013b1493 | ||
|
|
5267fa6d2f | ||
|
|
7f40cbdeed | ||
|
|
b590c9402d | ||
|
|
9546feda11 | ||
|
|
3587f82976 | ||
|
|
94a3962590 | ||
|
|
3b2d79a451 | ||
|
|
da56a645af | ||
|
|
af4e55af1d | ||
|
|
d6a08a8f82 | ||
|
|
c73eb8869f | ||
|
|
929fc97ac6 | ||
|
|
7594a75bea | ||
|
|
ec36a056b2 | ||
|
|
8d30f6ef15 | ||
|
|
08d1e3164f | ||
|
|
a5f3ef20fe | ||
|
|
467445fe05 | ||
|
|
d0448f844a | ||
|
|
b2299a5f2f | ||
|
|
9b42754c69 | ||
|
|
aee33334ac | ||
|
|
cc530fb12d | ||
|
|
19877f1b29 | ||
|
|
e9880bcf68 | ||
|
|
4c7111dcb6 | ||
|
|
e81cfdcce1 | ||
|
|
3eba3f414f | ||
|
|
fbeb8dfd91 | ||
|
|
dda3e8baaf | ||
|
|
c1e4f0b18a | ||
|
|
b0d9f3a468 | ||
|
|
5b117acaa9 | ||
|
|
a5cb607650 | ||
|
|
fe626155c5 | ||
|
|
debbe1c1b1 | ||
|
|
f937a6efe2 | ||
|
|
30cb3c87eb | ||
|
|
fa46294154 | ||
|
|
72554fb27f | ||
|
|
4e6985b572 | ||
|
|
4477647613 | ||
|
|
2b3066abc9 | ||
|
|
da1bcb38da | ||
|
|
4a7524422d | ||
|
|
275af03195 | ||
|
|
0c75cf4619 | ||
|
|
80de277529 | ||
|
|
98f669db66 | ||
|
|
2c8dab5927 | ||
|
|
cb66d4f6c0 | ||
|
|
42a39ee891 | ||
|
|
c9bfe68ea5 | ||
|
|
8cb7144ff4 | ||
|
|
68cc58e2fe | ||
|
|
cbe5556ac9 | ||
|
|
eb80967608 | ||
|
|
66fea0722d | ||
|
|
c917061b97 | ||
|
|
da39a07f25 | ||
|
|
f2f65285de | ||
|
|
cac8f93330 | ||
|
|
bb03b18f4a | ||
|
|
ab280ee152 | ||
|
|
16c8137160 | ||
|
|
b8b903c8cd | ||
|
|
c98a5ff896 | ||
|
|
d68e436a8b | ||
|
|
8efa6790d6 | ||
|
|
a381f50e88 | ||
|
|
d3040cd5a8 | ||
|
|
f726a9883f | ||
|
|
c18a7baa37 | ||
|
|
d638d518f8 | ||
|
|
bdd371ffe8 | ||
|
|
57e6e49c7c | ||
|
|
a5c8de496d | ||
|
|
d478f32760 | ||
|
|
5d8e908339 | ||
|
|
606bae016b | ||
|
|
6910a2f04c | ||
|
|
8bee1788c1 | ||
|
|
b10459cb15 | ||
|
|
3b48dd1613 | ||
|
|
e5cef90a64 | ||
|
|
bac74c9577 | ||
|
|
19d748ae09 | ||
|
|
4db865371f | ||
|
|
75e8b3bf4b | ||
|
|
194cdc66b7 | ||
|
|
53059c0a0d | ||
|
|
ed4ffc8a86 | ||
|
|
9967c172da | ||
|
|
0aa9c065a5 | ||
|
|
d0c2abd38d | ||
|
|
cb98099a91 | ||
|
|
f446e0d5b1 | ||
|
|
17de31f1a6 | ||
|
|
d81a466089 | ||
|
|
9e597fadba | ||
|
|
1e6aa0f4c2 | ||
|
|
41842f7c6a | ||
|
|
0d5564990d | ||
|
|
7f9755493a | ||
|
|
fea587ad36 | ||
|
|
b2b7537fee | ||
|
|
57f99fe7af | ||
|
|
01263017e7 | ||
|
|
e66f9a7244 | ||
|
|
54cb3f8e7a | ||
|
|
4c861cf5e4 | ||
|
|
a89f57e690 | ||
|
|
d5141e5b6a | ||
|
|
8aded98a11 | ||
|
|
0b5a0d0d7e | ||
|
|
5a36762544 | ||
|
|
a4b7573ce7 | ||
|
|
6fc8c0b4a8 | ||
|
|
ced2a97547 | ||
|
|
f579171024 | ||
|
|
885e5f4568 | ||
|
|
81d8cd8a07 | ||
|
|
f153f41357 | ||
|
|
2b9e3ff3bc | ||
|
|
d15cc4642a | ||
|
|
3c4f45678a | ||
|
|
b312849fff | ||
|
|
10580dfa03 | ||
|
|
62d0e8b041 | ||
|
|
17cdaac380 | ||
|
|
e4eb388e18 | ||
|
|
d94c5dee4e | ||
|
|
4bea0816b6 | ||
|
|
fb491328d6 | ||
|
|
4e043d93ce | ||
|
|
0b525b835f | ||
|
|
3c8c39ec26 | ||
|
|
36997403e1 | ||
|
|
1175591521 | ||
|
|
04accb6689 | ||
|
|
58e3c7be94 | ||
|
|
7a6db36554 | ||
|
|
80ebd98f6f | ||
|
|
9e0495fc26 | ||
|
|
f2437a8b3c | ||
|
|
9e87562e23 | ||
|
|
8f5cb13cc3 | ||
|
|
e0a0ec9911 | ||
|
|
419c063c6c | ||
|
|
2aed66d371 | ||
|
|
1f45436c13 | ||
|
|
df8520afd3 | ||
|
|
95187e6b80 | ||
|
|
c8a3ace052 | ||
|
|
c059ac45bf | ||
|
|
f4b8addda4 | ||
|
|
aa3a1cc300 | ||
|
|
0a5f138829 | ||
|
|
9f427f5873 | ||
|
|
bacbf42a04 | ||
|
|
56a9d52466 | ||
|
|
2c964253ec | ||
|
|
e886bea05c | ||
|
|
66affed338 | ||
|
|
a8ae07e6c4 | ||
|
|
0df286a5f7 | ||
|
|
519a8feb14 | ||
|
|
16cb00f46f | ||
|
|
3c59602666 | ||
|
|
3e0dbc7401 | ||
|
|
a24d29b56b | ||
|
|
deb32b5c0f | ||
|
|
d4a6d2fb4a | ||
|
|
01ea0e9561 | ||
|
|
9676b51f01 | ||
|
|
b0d2f86eaf | ||
|
|
f356efb42e | ||
|
|
559d98e884 | ||
|
|
d8623b88df | ||
|
|
7044d6606e | ||
|
|
92aab57d37 | ||
|
|
f661055ae8 | ||
|
|
d310ec2b93 | ||
|
|
301ad369c0 | ||
|
|
4c94a51a9d | ||
|
|
a2c1d79247 | ||
|
|
e5d9ac2fbf | ||
|
|
67a15b36b3 | ||
|
|
f396cdb2d3 | ||
|
|
9cc538cdcd | ||
|
|
01da6becff | ||
|
|
003c9b176c | ||
|
|
0a5315fb95 | ||
|
|
5a4d578d13 | ||
|
|
f1345472b5 | ||
|
|
60214f0e57 | ||
|
|
3a2ce72910 | ||
|
|
ea9c0502b7 | ||
|
|
074d3ce201 | ||
|
|
05c80e2497 | ||
|
|
8a363dd815 | ||
|
|
1e5fe59d9c | ||
|
|
1a2b9617fe | ||
|
|
1f92510a43 | ||
|
|
f7fceb1ea2 | ||
|
|
2aec60a354 | ||
|
|
9bc42a4af0 | ||
|
|
5c98cc1748 | ||
|
|
9ae14ab04a | ||
|
|
5d014f4669 | ||
|
|
5e8dea7255 | ||
|
|
38fc9f521b | ||
|
|
80d7ac6ff9 | ||
|
|
9bacd7a0f5 | ||
|
|
c89ac40039 | ||
|
|
6d6cf590ee | ||
|
|
f063a8f30e | ||
|
|
03536a2180 | ||
|
|
0efd5cc257 | ||
|
|
9439f13b7b | ||
|
|
d936806406 | ||
|
|
9d7ccd4201 | ||
|
|
c8a4c1aae6 | ||
|
|
406db7ccff | ||
|
|
f48a35c7ce | ||
|
|
8d25df74b7 | ||
|
|
831a03402e | ||
|
|
a631338599 | ||
|
|
d943721b4a | ||
|
|
1bbf978cc4 | ||
|
|
856f913ce6 | ||
|
|
dc617b6925 | ||
|
|
3f80062672 | ||
|
|
07eb423814 | ||
|
|
721232448e | ||
|
|
0b0d261aae | ||
|
|
87a8d741d7 | ||
|
|
d03dd6129a | ||
|
|
96c20995a3 | ||
|
|
82470553db | ||
|
|
2408505889 | ||
|
|
7d85693a1f | ||
|
|
d86a011bfd | ||
|
|
4d54ac744b | ||
|
|
132614b551 | ||
|
|
17c541548a | ||
|
|
d3f7664116 | ||
|
|
2ea40004a6 | ||
|
|
82ec2d9493 | ||
|
|
53bef174b3 | ||
|
|
bcfef8359e | ||
|
|
5f18411aed | ||
|
|
1102a6e8e5 | ||
|
|
867aec092b | ||
|
|
ea9e727fcf | ||
|
|
099476f618 | ||
|
|
db78494b1a | ||
|
|
e7ad6e5a7b | ||
|
|
3d7d051df2 | ||
|
|
8e827dc934 | ||
|
|
12b764710a | ||
|
|
b5c95874bb | ||
|
|
b896b1a9b8 | ||
|
|
cf99acc682 | ||
|
|
7fb20964dc | ||
|
|
8751117946 | ||
|
|
10c4ed179b | ||
|
|
8e13541b24 | ||
|
|
4f6a9d4b66 | ||
|
|
56411496c2 | ||
|
|
e55a1cb657 | ||
|
|
3850d4b8ae | ||
|
|
1626d85d31 | ||
|
|
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 | ||
|
|
4e6883a404 | ||
|
|
00687dc84e | ||
|
|
61a1939c02 | ||
|
|
43cec9b6fa | ||
|
|
97214280bc | ||
|
|
0de4a72388 | ||
|
|
f65f1d01ce | ||
|
|
6161865f1a | ||
|
|
d28684a9d0 | ||
|
|
54c2a41466 | ||
|
|
037f5cfec0 | ||
|
|
22631478b9 | ||
|
|
349ae780a7 | ||
|
|
60fb2ee7cb |
+1
-1
@@ -8,7 +8,7 @@ module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
|
||||
@@ -3,14 +3,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
description: "Deployment environment"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- stg
|
||||
- prd
|
||||
|
||||
@@ -26,9 +26,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 +41,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 +80,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 +95,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 +110,43 @@ 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
|
||||
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
|
||||
# - 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: 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
|
||||
|
||||
k8s-deploy:
|
||||
needs: [extract_environment, semantic_release, build_ecr_image]
|
||||
uses: ./.github/workflows/k8s-deploy.yml
|
||||
with:
|
||||
cloud: 'oracle'
|
||||
environment: ${{ needs.extract_environment.outputs.environment }}
|
||||
image: ${{ needs.semantic_release.outputs.new_release_version }}
|
||||
secrets: inherit
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
name : K8s deploy
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
cloud:
|
||||
description: "Cloud provider for the deployment"
|
||||
required: true
|
||||
default: "azure"
|
||||
type: string
|
||||
environment:
|
||||
description: "Deployment environment"
|
||||
required: true
|
||||
default: "prd"
|
||||
type: string
|
||||
image:
|
||||
description: "Image Tag"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
azure:
|
||||
if: inputs.cloud == 'azure'
|
||||
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: Authenticate with cluster
|
||||
env:
|
||||
CLUSTER_NAME: platform-${{ inputs.environment }}
|
||||
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v1
|
||||
with:
|
||||
version: 'v1.30.1'
|
||||
|
||||
- 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: Run Helmfile Apply
|
||||
env:
|
||||
ENV: ${{ inputs.environment }}
|
||||
IMAGE_TAG: ${{ inputs.image }}
|
||||
run: helmfile -f deploy/helmfiles/${ENV}.yaml sync --set image.tag=$IMAGE_TAG
|
||||
|
||||
oracle:
|
||||
if: inputs.cloud == 'oracle'
|
||||
runs-on: [self-hosted, "prd-oracle"]
|
||||
env:
|
||||
HOME: /home/runner
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: 'v3.9.0'
|
||||
|
||||
- name: Install OCI CLI
|
||||
env:
|
||||
HOME: /home/runner
|
||||
run: |
|
||||
bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" -- --accept-all-defaults
|
||||
echo "$HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Configure OCI CLI
|
||||
run: |
|
||||
mkdir -p ~/.oci || true
|
||||
echo "${{ secrets.OCI_CONFIG }}" > ~/.oci/config
|
||||
echo "${{ secrets.OCI_PRIVATE_KEY }}" > ~/.oci/oci_api_key.pem
|
||||
chmod 600 ~/.oci/oci_api_key.pem
|
||||
|
||||
- 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
|
||||
sudo mv helmfile /usr/local/bin/
|
||||
helmfile --version
|
||||
|
||||
- name: Install Helm Diff Plugin
|
||||
run: helm plugin install https://github.com/databus23/helm-diff || true
|
||||
|
||||
- name: Authenticate with OKE cluster
|
||||
env:
|
||||
ENV: ${{ inputs.environment }}
|
||||
STG_CLUSTER_ID: "ocid1.cluster.oc1.sa-saopaulo-1.aaaaaaaagh3jvln52a3ebm3dodx6emmhv5bmfs7i7sv2k4zkbcbrzcl6v37q"
|
||||
PRD_CLUSTER_ID: "ocid1.cluster.oc1.sa-saopaulo-1.aaaaaaaanf3vptl6hc2tzd4enfd2hfpsht3wikxww5xejc3l7cwfm6l3sndq"
|
||||
run: |
|
||||
if [ "$ENV" = "stg" ]; then
|
||||
CLUSTER_ID=$STG_CLUSTER_ID
|
||||
elif [ "$ENV" = "prd" ]; then
|
||||
CLUSTER_ID=$PRD_CLUSTER_ID
|
||||
else
|
||||
echo "Unknown environment: $ENV"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
oci ce cluster create-kubeconfig --cluster-id ${CLUSTER_ID} --file $HOME/.kube/config --region sa-saopaulo-1 --token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT
|
||||
|
||||
- name: Run Helmfile Apply
|
||||
env:
|
||||
ENV: ${{ inputs.environment }}
|
||||
IMAGE_TAG: ${{ inputs.image }}
|
||||
run: helmfile -f deploy/helmfiles/${ENV}.yaml sync --set image.tag=$IMAGE_TAG
|
||||
@@ -6,23 +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
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
APP_NAME: ${{ github.event.repository.name }}
|
||||
run: |
|
||||
docker build -t $APP_NAME --target test .
|
||||
run: docker build -t ${APP_NAME}_teste --target test .
|
||||
|
||||
- name: Run Test
|
||||
env:
|
||||
ENV: test
|
||||
APP_NAME: ${{ github.event.repository.name }}
|
||||
run: |
|
||||
docker run --rm --entrypoint="npm" $APP_NAME run test
|
||||
run: docker run ${APP_NAME}_teste
|
||||
|
||||
- name: Remove Docker's Trash
|
||||
if: always()
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
name: Validate K8S Modifications
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
|
||||
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-check:
|
||||
env:
|
||||
HOME: /home/runner
|
||||
needs: [extract_environment]
|
||||
environment: ${{ needs.extract_environment.outputs.environment }}
|
||||
runs-on: [self-hosted, "prd-oracle"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: 'v3.9.0'
|
||||
|
||||
- name: Determine DNS_HOST based on environment
|
||||
id: set_dns
|
||||
env:
|
||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||
run: |
|
||||
if [ "$ENV" = "prd" ]; then
|
||||
echo "dns_host=dadosfera.ai" >> $GITHUB_OUTPUT
|
||||
elif [ "$ENV" = "stg" ]; then
|
||||
echo "dns_host=stg.dadosfera.ai" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Install OCI CLI
|
||||
run: |
|
||||
bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" -- --accept-all-defaults
|
||||
echo "$HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Configure OCI CLI
|
||||
run: |
|
||||
mkdir -p ~/.oci || true
|
||||
echo "${{ secrets.OCI_CONFIG }}" > ~/.oci/config
|
||||
echo "${{ secrets.OCI_PRIVATE_KEY }}" > ~/.oci/oci_api_key.pem
|
||||
chmod 600 ~/.oci/oci_api_key.pem
|
||||
|
||||
- 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
|
||||
sudo mv helmfile /usr/local/bin/
|
||||
helmfile --version
|
||||
|
||||
- name: Install Helm Diff Plugin
|
||||
run: helm plugin install https://github.com/databus23/helm-diff || true
|
||||
|
||||
- name: Authenticate with OKE cluster
|
||||
env:
|
||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||
STG_CLUSTER_ID: "ocid1.cluster.oc1.sa-saopaulo-1.aaaaaaaagh3jvln52a3ebm3dodx6emmhv5bmfs7i7sv2k4zkbcbrzcl6v37q"
|
||||
PRD_CLUSTER_ID: "ocid1.cluster.oc1.sa-saopaulo-1.aaaaaaaanf3vptl6hc2tzd4enfd2hfpsht3wikxww5xejc3l7cwfm6l3sndq"
|
||||
run: |
|
||||
if [ "$ENV" = "stg" ]; then
|
||||
CLUSTER_ID=$STG_CLUSTER_ID
|
||||
elif [ "$ENV" = "prd" ]; then
|
||||
CLUSTER_ID=$PRD_CLUSTER_ID
|
||||
else
|
||||
echo "Unknown environment: $ENV"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
oci ce cluster create-kubeconfig --cluster-id ${CLUSTER_ID} --file $HOME/.kube/config --region sa-saopaulo-1 --token-version 2.0.0 --kube-endpoint PRIVATE_ENDPOINT
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v1
|
||||
with:
|
||||
version: 'v1.30.1'
|
||||
|
||||
- name: Run Helmfile Diff
|
||||
env:
|
||||
ENV: ${{ needs.extract_environment.outputs.environment }}
|
||||
run: helmfile -f deploy/helmfiles/${ENV}.yaml diff
|
||||
+1
-1
@@ -37,4 +37,4 @@ lerna-debug.log*
|
||||
# Ignore .env files
|
||||
*.env
|
||||
|
||||
.env
|
||||
.env
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+61
-16
@@ -1,21 +1,66 @@
|
||||
# install all dependencies
|
||||
FROM node:18.3.0-alpine3.15 as packages
|
||||
FROM node:18.17-alpine AS base_image
|
||||
|
||||
FROM base_image AS build_base
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN apk add --no-cache aws-cli \
|
||||
&& aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1 \
|
||||
&& npm install
|
||||
RUN apk update
|
||||
# needed packages to build dependencies from source
|
||||
RUN apk add --no-cache \
|
||||
aws-cli \
|
||||
chromium \
|
||||
nss \
|
||||
freetype \
|
||||
harfbuzz \
|
||||
ca-certificates \
|
||||
ttf-freefont
|
||||
COPY package*.json ./
|
||||
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||
|
||||
|
||||
# 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 node:18.3.0-alpine3.15 as test
|
||||
WORKDIR /app
|
||||
COPY --from=packages /app/node_modules ./node_modules
|
||||
COPY . ./
|
||||
FROM ci_image AS test
|
||||
ENV DUC_URL=0.0.0.0:50051
|
||||
ENTRYPOINT ["npm", "run", "test"]
|
||||
|
||||
FROM node:18.3.0-alpine3.15
|
||||
WORKDIR /app
|
||||
COPY . /app/
|
||||
COPY --from=packages /app/node_modules ./node_modules
|
||||
|
||||
# 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 ./
|
||||
RUN apk update
|
||||
# needed packages to build dependencies from source
|
||||
RUN apk add --no-cache \
|
||||
chromium \
|
||||
nss \
|
||||
freetype \
|
||||
harfbuzz \
|
||||
ca-certificates \
|
||||
ttf-freefont
|
||||
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||
|
||||
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
|
||||
@@ -1,68 +1,103 @@
|
||||
<p align="center">
|
||||
<image src="./assets/maestro.svg" style="width:10rem">
|
||||
</p>
|
||||
</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}
|
||||
|
||||
Binary file not shown.
@@ -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,126 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Values.app_name }}
|
||||
namespace: applications
|
||||
labels:
|
||||
app: {{ .Values.app_name }}
|
||||
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Values.app_name }}
|
||||
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ .Values.app_name }}
|
||||
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: {{ .Values.imagePullSecrets }}
|
||||
nodeSelector:
|
||||
"beta.kubernetes.io/os": linux
|
||||
{{- if .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml .Values.affinity | nindent 8 }}
|
||||
{{- end }}
|
||||
|
||||
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 }}
|
||||
{{- if .Values.resources }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
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: LOGGER_CONSOLE_EXTRA
|
||||
value: "true"
|
||||
- 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: OPEN_CUSTOMER_ID
|
||||
value: {{ .Values.maestro.open_customer_id }}
|
||||
- name: OPEN_GROUP_ID
|
||||
value: {{ .Values.maestro.open_group_id }}
|
||||
- name: DEDICATED_PROXY
|
||||
value: {{ .Values.maestro.dedicated_proxy }}
|
||||
- name: COOKIE_SECRET
|
||||
value: {{ .Values.maestro.cookie_secret }}
|
||||
- name: REDIS_DATABASE
|
||||
value: "{{ .Values.maestro.redis_database }}"
|
||||
- name: REDIS_HOST
|
||||
value: {{ .Values.maestro.redis_host }}
|
||||
- name: REDIS_PORT
|
||||
value: "{{ .Values.maestro.redis_port }}"
|
||||
- name: JWT_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prd-duc
|
||||
key: jwt_token
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prd-{{ .Values.app_name }}
|
||||
key: AWS_ACCESS_KEY_ID
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prd-{{ .Values.app_name }}
|
||||
key: AWS_SECRET_ACCESS_KEY
|
||||
- name: AWS_DEFAULT_REGION
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: prd-{{ .Values.app_name }}
|
||||
key: AWS_DEFAULT_REGION
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/whitelist-source-range: "69.49.241.121/32" # hostgator ip
|
||||
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: {{ .Values.app_name }}
|
||||
{{- if .Values.maestro.dedicated_proxy}}
|
||||
name: open-data-{{ .Values.app_name }}
|
||||
{{- else }}
|
||||
name: open-data
|
||||
{{- end }}
|
||||
namespace: applications
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: {{ .Values.hostname }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ .Values.app_name }}
|
||||
port:
|
||||
number: {{ .Values.ingress.port }}
|
||||
path: /open-data/sharing-ocean-data
|
||||
pathType: Prefix
|
||||
@@ -0,0 +1,36 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
underscores_in_headers on;
|
||||
ignore_invalid_headers on;
|
||||
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
|
||||
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
|
||||
nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "64k"
|
||||
{{- if .Values.maestro.restricted_ip}}
|
||||
nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.maestro.restricted_ip }}
|
||||
{{- end }}
|
||||
|
||||
generation: 1
|
||||
labels:
|
||||
app: {{ .Values.app_name }}
|
||||
name: {{ .Values.app_name }}
|
||||
namespace: applications
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: {{ .Values.hostname }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ .Values.app_name }}
|
||||
port:
|
||||
number: {{ .Values.ingress.port }}
|
||||
path: /
|
||||
pathType: Prefix
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: prd-{{ .Values.app_name }}
|
||||
namespace: applications
|
||||
labels:
|
||||
app: {{ .Values.app_name }}
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: secretsmanager-prd
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: prd-{{ .Values.app_name }}
|
||||
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: {{ .Values.app_name }}
|
||||
namespace: applications
|
||||
labels:
|
||||
app: {{ .Values.app_name }}
|
||||
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: {{ .Values.app_name }}
|
||||
protocol: TCP
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.service.targetPort }}
|
||||
selector:
|
||||
app: {{ .Values.app_name }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
maestro:
|
||||
env: stg
|
||||
duc_url: duc.stg.dadosfera.ai
|
||||
pi_factory_url: pi-factory.stg.dadosfera.ai
|
||||
in_factory_url: in-factory.stg.dadosfera.ai
|
||||
tr_factory_url: in-factory.stg.dadosfera.ai
|
||||
open_customer_id: b3e3dfe5-b992-4586-a73c-c0b0c00f615d
|
||||
open_group_id: e3f98a2f-7748-4981-8505-7695c8ca8218
|
||||
cookie_secret: "ff7bc13823edb2ae50d248e5780bddc9d4b31c36"
|
||||
redis_database: "1"
|
||||
|
||||
hostname: maestro.stg.dadosfera.ai
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
affinity: null
|
||||
@@ -0,0 +1,67 @@
|
||||
# Default values for metabase.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 3
|
||||
hostname: maestro.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
|
||||
app_name: maestro
|
||||
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
|
||||
dedicated_proxy: ""
|
||||
restricted_ip: ""
|
||||
redis_host: "aaapzppmlyamkocqwstpo7zvopczyyiyuy6xzm2g6c5k4mq3a66be4a-0.redis.sa-saopaulo-1.oci.oraclecloud.com"
|
||||
redis_port: "6379"
|
||||
redis_database: "0"
|
||||
cookie_secret: "13cc5e136d3074bcc05bec8697092ec1f5f376bf"
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetCPUUtilizationPercentage: 80
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: name
|
||||
operator: In
|
||||
values:
|
||||
- product
|
||||
@@ -0,0 +1,56 @@
|
||||
releases:
|
||||
- name: maestro
|
||||
chart: ../helm-chart
|
||||
values:
|
||||
- ../helm-chart/values.yaml
|
||||
set:
|
||||
- name: app_name
|
||||
value: maestro
|
||||
- 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: b3e3dfe5-b992-4586-a73c-c0b0c00f615d
|
||||
- name: maestro.open_group_id
|
||||
value: c0afdcce-c5be-40d0-9d1d-2d271121f14a
|
||||
- name: replicaCount
|
||||
value: 2
|
||||
|
||||
- name: unimed-maestro
|
||||
chart: ../helm-chart
|
||||
values:
|
||||
- ../helm-chart/values.yaml
|
||||
set:
|
||||
- name: app_name
|
||||
value: maestro-unimed
|
||||
- name: maestro.duc_url
|
||||
value: duc.dadosfera.ai
|
||||
- name: hostname
|
||||
value: maestro-unimed.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: c0afdcce-c5be-40d0-9d1d-2d271121f14a
|
||||
# Customer id
|
||||
- name: maestro.dedicated_proxy
|
||||
value: dea2c27f-0973-4588-a2e0-9e31b64c7ffd
|
||||
- name: replicaCount
|
||||
value: 1
|
||||
# 10.70.0.0/16 internal network
|
||||
# 137.131.167.254/32 loadbalancer
|
||||
# 159.112.184.81/32 cluster ip for the uptime request ingest
|
||||
- name: maestro.restricted_ip
|
||||
value: "177.52.172.0/24, 189.84.160.157/32, 186.237.171.146/32, 137.131.167.254/32, 10.70.0.0/16, 159.112.184.81/32, 10.244.0.0/16"
|
||||
@@ -0,0 +1,30 @@
|
||||
charts:
|
||||
- name: maestro
|
||||
chart: ../helm-chart
|
||||
values:
|
||||
- ../helm-chart/values.yaml
|
||||
- ../helm-chart/values-stg.yaml
|
||||
|
||||
|
||||
# Environment to test Network Policies
|
||||
- name: private-maestro
|
||||
chart: ../helm-chart
|
||||
values:
|
||||
- ../helm-chart/values.yaml
|
||||
- ../helm-chart/values-stg.yaml
|
||||
set:
|
||||
- name: app_name
|
||||
value: maestro-private
|
||||
- name: hostname
|
||||
value: private-maestro.stg.dadosfera.ai
|
||||
# Customer id
|
||||
- name: maestro.dedicated_proxy
|
||||
value: 14d52fd4-d83d-4cdd-be34-bf11cc28b3bd
|
||||
- name: replicaCount
|
||||
value: 1
|
||||
- name: affinity
|
||||
value: null
|
||||
- name: resources
|
||||
value: null
|
||||
- name: maestro.restricted_ip
|
||||
value: "137.131.167.254/32, 10.70.0.0/16, 159.112.184.81/32, 10.244.0.0/16"
|
||||
@@ -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,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
+9824
File diff suppressed because it is too large
Load Diff
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
DEDICATED_PROXY: string;
|
||||
COOKIE_SECRET: string;
|
||||
|
||||
// Autodrive Configuration
|
||||
AUTODRIVE_USERNAME?: string;
|
||||
AUTODRIVE_PASSWORD?: string;
|
||||
AUTODRIVE_BASE_URL?: string;
|
||||
AUTODRIVE_MODEL?: string;
|
||||
AUTODRIVE_KEY?: string;
|
||||
AUTO_DRIVE_KEY?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
export {};
|
||||
@@ -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;
|
||||
@@ -4,6 +4,7 @@
|
||||
"compilerOptions": {
|
||||
"assets": [
|
||||
"**/*.proto",
|
||||
"assets/**/*",
|
||||
{
|
||||
"include": "i18n/**/*",
|
||||
"watchAssets": true
|
||||
|
||||
Generated
+6210
-11012
File diff suppressed because it is too large
Load Diff
+63
-69
@@ -6,111 +6,105 @@
|
||||
"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/dadosfera-logs": "^1.0.0-beta.3",
|
||||
"@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.38.0-beta.10",
|
||||
"@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",
|
||||
"cache-manager": "^5.1.4",
|
||||
"cache-manager-ioredis-yet": "^1.1.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cron-parser": "^4.9.0",
|
||||
"csv": "^6.3.11",
|
||||
"dotenv": "^14.3.2",
|
||||
"elastic-apm-node": "^3.36.0",
|
||||
"helmet": "^5.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"elastic-apm-node": "^3.50.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"helmet": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.5",
|
||||
"mixpanel": "^0.17.0",
|
||||
"ms": "^3.0.0-canary.1",
|
||||
"openid-client": "^5.7.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.2",
|
||||
"@dadosfera/protospack-v2": "3.10.0",
|
||||
"puppeteer": "^24.7.2",
|
||||
"redis": "^4.5.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/cookie-parser": "^1.4.9",
|
||||
"@types/cache-manager": "^4.0.6",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.18.1",
|
||||
"@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": ".",
|
||||
"modulePaths": [
|
||||
"<rootDir>"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
+48
-85
@@ -1,81 +1,42 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { InputsController } from './modules/inputs/inputs.controller';
|
||||
import { TransformationsController } from './modules/transformations/transformations.controller';
|
||||
import { OutputsController } from './modules/outputs/outputs.controllers';
|
||||
import { PipelinesController } from './modules/pipelines/pipelines.controller';
|
||||
import { HealthController } from './modules/health/health.controller';
|
||||
|
||||
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 { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
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 { OutputsClientConfiguration } from './clients/outputs/client.config';
|
||||
import { TransformationsClientConfiguration } from './clients/transformations/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 { UsersModule } from './modules/users/users.module';
|
||||
import { RolesModule } from './modules/roles/roles.module';
|
||||
|
||||
import { ConnectorModule } from './modules/connector/connector.module';
|
||||
import { AuthModule } from './modules/auth/auth.module';
|
||||
import { PermissionsModule } from './modules/permissions/permissions.module';
|
||||
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { TermsOfUseModule } from './modules/termsOfUse/termsOfUse.module';
|
||||
|
||||
const inputClient = new InputsClientConfiguration();
|
||||
const outputClient = new OutputsClientConfiguration();
|
||||
const pipelineClient = new PipelinesClientConfiguration();
|
||||
const transformationClient = new TransformationsClientConfiguration();
|
||||
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';
|
||||
import { IdentityProviderModule } from './modules/identity-provider/identity-provider.module';
|
||||
import { NetworkPolicyModule } from './modules/network-policy/network-policy.module';
|
||||
import { AssignModule } from './modules/assign/assign.module';
|
||||
import { ShareMetadataModule } from './modules/share-metadata/share-metadata.module';
|
||||
import { ApiKeyModule } from './modules/api-key/api-key.module';
|
||||
|
||||
@Module({
|
||||
controllers: [
|
||||
InputsController,
|
||||
TransformationsController,
|
||||
OutputsController,
|
||||
PipelinesController,
|
||||
HealthController,
|
||||
CatalogController,
|
||||
OauthController,
|
||||
],
|
||||
providers: [
|
||||
DadosferaLogger,
|
||||
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
|
||||
InputsService,
|
||||
TransformationsService,
|
||||
OutputsService,
|
||||
PipelinesService,
|
||||
HealthService,
|
||||
InputsClientService,
|
||||
TransformationsClientService,
|
||||
OutputsClientService,
|
||||
PipelinesClientService,
|
||||
CatalogService,
|
||||
HubspotStrategy,
|
||||
FacebookStrategy,
|
||||
GoogleStrategy,
|
||||
MailChimpStrategy,
|
||||
SalesforceStrategy,
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthenticationGuard,
|
||||
@@ -85,33 +46,35 @@ const transformationClient = new TransformationsClientConfiguration();
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ConnectorModule,
|
||||
AuthModule,
|
||||
ConnectionModule,
|
||||
NetworkConfigModule,
|
||||
InputsModule,
|
||||
PipelinesV2Module,
|
||||
CatalogModule,
|
||||
ConnectorModule,
|
||||
PermissionsModule,
|
||||
TermsOfUseModule,
|
||||
|
||||
ClientsModule.register([
|
||||
{
|
||||
name: 'INPUTS_PACKAGE',
|
||||
...inputClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'TRANSFORMATIONS_PACKAGE',
|
||||
...transformationClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'OUTPUTS_PACKAGE',
|
||||
...outputClient.config(),
|
||||
},
|
||||
{
|
||||
name: 'PIPELINES_PACKAGE',
|
||||
...pipelineClient.config(),
|
||||
},
|
||||
]),
|
||||
|
||||
ConnectionTestModule,
|
||||
PipelinesModule,
|
||||
TransformationsModule,
|
||||
UsersModule,
|
||||
|
||||
RolesModule,
|
||||
OauthModule,
|
||||
ProductboardModule,
|
||||
MixpanelModule,
|
||||
CustomersModule,
|
||||
OpenDataModule,
|
||||
ThemeModule,
|
||||
NetworkPolicyModule,
|
||||
AssignModule,
|
||||
ShareMetadataModule,
|
||||
NetworkPolicyModule,
|
||||
ApiKeyModule,
|
||||
IdentityProviderModule,
|
||||
NetworkPolicyModule,
|
||||
//Always leave HealthModule last, so it is on the bottom of swagger
|
||||
HealthModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dadosfera Relatório de PII</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
@page {
|
||||
size: A4 landscape; /* Alterado para paisagem (landscape) */
|
||||
margin: 15mm 10mm; /* Reduzido para proporcionar mais espaço */
|
||||
}
|
||||
body {
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
color: #5c5c5c;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
font-size: 12px; /* Reduzindo o tamanho da fonte */
|
||||
}
|
||||
.container {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo {
|
||||
max-width: 150px; /* Reduzida para economizar espaço */
|
||||
height: auto;
|
||||
}
|
||||
h1 {
|
||||
color: #0d003b;
|
||||
font-weight: 700;
|
||||
margin-left: 20px;
|
||||
font-size: 24px; /* Tamanho ajustado */
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
table-layout: fixed; /* Importante: define larguras fixas */
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
border: 1px solid #d0d0d0;
|
||||
}
|
||||
th {
|
||||
background-color: #1700a2;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #3a26b8;
|
||||
font-size: 11px; /* Tamanho ajustado */
|
||||
word-wrap: break-word; /* Permite quebra de palavras */
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #d0d0d0;
|
||||
font-size: 11px; /* Tamanho ajustado */
|
||||
word-wrap: break-word; /* Permite quebra de palavras */
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
/* Definindo larguras específicas para cada coluna */
|
||||
th:nth-child(1), td:nth-child(1) { width: 14%; } /* Database */
|
||||
th:nth-child(2), td:nth-child(2) { width: 14%; } /* Schema */
|
||||
th:nth-child(3), td:nth-child(3) { width: 17%; } /* Tabela */
|
||||
th:nth-child(4), td:nth-child(4) { width: 17%; } /* Coluna */
|
||||
th:nth-child(5), td:nth-child(5) { width: 13%; } /* Tipo de Dado */
|
||||
th:nth-child(6), td:nth-child(6) { width: 25%; } /* Regras PII */
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
tr:nth-child(odd) {
|
||||
background-color: white;
|
||||
}
|
||||
.info-section {
|
||||
margin-top: 20px;
|
||||
color: #5c5c5c;
|
||||
}
|
||||
.timestamp {
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
margin-top: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="https://dadosfera.ai/wp-content/webp-express/webp-images/uploads/2022/06/Logo-Dadosfera1-1.png.webp" alt="Logo Dadosfera" class="logo">
|
||||
<h1>Relatório de PII</h1>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<p>Este relatório apresenta a estrutura de tabelas e suas as seguintes características de PII identificadas.</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Database</th>
|
||||
<th>Schema</th>
|
||||
<th>Tabela</th>
|
||||
<th>Coluna</th>
|
||||
<th>Tipo de Dado</th>
|
||||
<th>Regras PII</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each dados}}
|
||||
<tr>
|
||||
<td>{{database_name}}</td>
|
||||
<td>{{table_schema}}</td>
|
||||
<td>{{table_name}}</td>
|
||||
<td>{{column_name}}</td>
|
||||
<td>{{data_type}}</td>
|
||||
<td>{{pii_rules}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="timestamp">Gerado em: {{dataGeracao}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Request } from 'express';
|
||||
import { CustomDecorator } from '@nestjs/common';
|
||||
import { PermissionsObjectPermission } from './permissions.enum';
|
||||
import { RequestUser } from './user.decorator';
|
||||
|
||||
export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__';
|
||||
|
||||
export type AuthenticationFunction = (req: Request, user: any) => boolean;
|
||||
|
||||
// implementation copied from SetMetadata, but tweaked to get existing values
|
||||
// of the metadataKey and accumulate it with the new metadataValue
|
||||
function SetMultipleMetadata(metadataKey, metadataValue): CustomDecorator {
|
||||
const decoratorFactory = (target, key, descriptor) => {
|
||||
// .start: tweak
|
||||
// descriptor?.value = function decorator; target = class decorator
|
||||
const accumulatedVal =
|
||||
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
|
||||
accumulatedVal.push(metadataValue);
|
||||
// .end: tweak
|
||||
|
||||
if (descriptor) {
|
||||
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
|
||||
return target;
|
||||
};
|
||||
|
||||
decoratorFactory.KEY = metadataKey;
|
||||
return decoratorFactory as any;
|
||||
}
|
||||
|
||||
export function RequireAllPermissions(
|
||||
...permissions: PermissionsObjectPermission[]
|
||||
) {
|
||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
|
||||
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
|
||||
);
|
||||
}
|
||||
|
||||
export function RequireSomePermission(
|
||||
...permissions: PermissionsObjectPermission[]
|
||||
) {
|
||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, (req, user: RequestUser) =>
|
||||
permissions.some(({ seqid }) => user.permissions.includes(seqid)),
|
||||
);
|
||||
}
|
||||
|
||||
export function AuthenticateCondition(func: AuthenticationFunction) {
|
||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, func);
|
||||
}
|
||||
|
||||
export function Authenticated() {
|
||||
return SetMultipleMetadata(AUTH_FUNCTION_KEY, () => true);
|
||||
}
|
||||
@@ -11,12 +11,13 @@ import {
|
||||
Authenticated,
|
||||
RequireAllPermissions,
|
||||
RequireSomePermission,
|
||||
} from './authentication.decorator';
|
||||
} from '../decorators/authentication.decorator';
|
||||
import { AuthenticationGuard } from './authentication.guard';
|
||||
import { PERMISSIONS } from './permissions.enum';
|
||||
import { PERMISSIONS_GROUPS } from './permissions.enum';
|
||||
import { AuthClientService } from '../modules/auth/auth.service';
|
||||
|
||||
import ErrorCodes from '../utils/errorCodes';
|
||||
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
|
||||
|
||||
const logger = {
|
||||
info: (...args) => args,
|
||||
@@ -36,15 +37,15 @@ class NoClassAuthController {
|
||||
}
|
||||
|
||||
@Get('required-permission')
|
||||
@RequireSomePermission(PERMISSIONS.ZENDESK.permissions.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
|
||||
async requiredPermission(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
|
||||
@Get('has-all-permissions')
|
||||
@RequireAllPermissions(
|
||||
PERMISSIONS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS.METABASE.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
)
|
||||
async hasAllPermissions(@Body() body) {
|
||||
return { body };
|
||||
@@ -52,8 +53,8 @@ class NoClassAuthController {
|
||||
|
||||
@Get('has-some-permission')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS.METABASE.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
)
|
||||
async hasSomePermission(@Body() body) {
|
||||
return { body };
|
||||
@@ -62,7 +63,7 @@ class NoClassAuthController {
|
||||
|
||||
@Controller('class-auth-condition')
|
||||
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
|
||||
@RequireSomePermission(PERMISSIONS.METABASE.permissions.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE)
|
||||
class ClassAuthConditionController {
|
||||
@Get('body')
|
||||
async getBody(@Body() body) {
|
||||
@@ -77,7 +78,7 @@ class ClassAuthConditionController {
|
||||
|
||||
@Get('required-permission')
|
||||
@AuthenticateCondition((req) => req.get('x-on-route') === 'ok')
|
||||
@RequireSomePermission(PERMISSIONS.ZENDESK.permissions.OPEN)
|
||||
@RequireSomePermission(PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN)
|
||||
async requiredPermission(@Body() body) {
|
||||
return { body };
|
||||
}
|
||||
@@ -99,6 +100,7 @@ describe('authentication.guard', () => {
|
||||
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
|
||||
customer_name: 'dadosfera',
|
||||
customer_tier: 'BASIC',
|
||||
customer_modules: []
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -120,6 +122,12 @@ describe('authentication.guard', () => {
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthenticationGuard,
|
||||
},
|
||||
{
|
||||
provide: ApiKeyService,
|
||||
useValue: {
|
||||
get: () => Promise.resolve(null)
|
||||
}
|
||||
}
|
||||
],
|
||||
controllers: [NoClassAuthController, ClassAuthConditionController],
|
||||
}).compile();
|
||||
@@ -440,18 +448,18 @@ describe('authentication.guard', () => {
|
||||
NoClassAuthTest(null, null);
|
||||
ClassAuthConditionTest(null, null);
|
||||
|
||||
const tokenZ = CreateToken([PERMISSIONS.ZENDESK.permissions.OPEN]);
|
||||
NoClassAuthTest(tokenZ, ['zendesk']);
|
||||
ClassAuthConditionTest(tokenZ, ['zendesk']);
|
||||
// const tokenZ = CreateToken([PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN]);
|
||||
// NoClassAuthTest(tokenZ, ['zendesk']);
|
||||
// ClassAuthConditionTest(tokenZ, ['zendesk']);
|
||||
|
||||
const tokenM = CreateToken([PERMISSIONS.METABASE.permissions.OPEN]);
|
||||
NoClassAuthTest(tokenM, ['metabase']);
|
||||
ClassAuthConditionTest(tokenM, ['metabase']);
|
||||
// const tokenM = CreateToken([PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE]);
|
||||
// NoClassAuthTest(tokenM, ['metabase']);
|
||||
// ClassAuthConditionTest(tokenM, ['metabase']);
|
||||
|
||||
const tokenZM = CreateToken([
|
||||
PERMISSIONS.ZENDESK.permissions.OPEN,
|
||||
PERMISSIONS.METABASE.permissions.OPEN,
|
||||
]);
|
||||
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
|
||||
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
|
||||
// const tokenZM = CreateToken([
|
||||
// PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
|
||||
// PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
|
||||
// ]);
|
||||
// NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
|
||||
// ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
OnApplicationBootstrap,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import assert from 'assert';
|
||||
@@ -13,10 +14,11 @@ 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';
|
||||
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationGuard
|
||||
@@ -31,6 +33,7 @@ export class AuthenticationGuard
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private reflector: Reflector,
|
||||
private authClient: AuthClientService,
|
||||
private apiKeyService: ApiKeyService
|
||||
) {
|
||||
this.pems = new Map();
|
||||
this.logger = dadosferaLogger.logger;
|
||||
@@ -48,20 +51,40 @@ export class AuthenticationGuard
|
||||
});
|
||||
}
|
||||
|
||||
canActivate(ctx: ExecutionContext): boolean {
|
||||
async canActivate(ctx: ExecutionContext): Promise<boolean> {
|
||||
const authFunctions = this.reflector.getAllAndMerge<
|
||||
AuthenticationFunction[]
|
||||
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
|
||||
const mustBeAuthenticated = authFunctions.length > 0;
|
||||
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
||||
|
||||
if (!mustBeAuthenticated) {
|
||||
// no need to be authenticated
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const apiKey = request.get('X-api-key');
|
||||
if (apiKey) {
|
||||
const {
|
||||
api_key
|
||||
} = await this.apiKeyService.get(apiKey);
|
||||
|
||||
request.user = {
|
||||
user_id: api_key.user_id,
|
||||
username: api_key.username,
|
||||
permissions: api_key.permissions,
|
||||
customer_id: api_key.customer_id,
|
||||
customer_name: api_key.customer_name,
|
||||
customer_tier: api_key.customer_tier,
|
||||
customer_modules: api_key.customer_modules,
|
||||
access_token: apiKey,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
||||
|
||||
if (!accessToken) {
|
||||
// couldn't load valid token
|
||||
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
||||
@@ -113,6 +136,18 @@ export class AuthenticationGuard
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bloquear outros customer de usar o maestor dedicado
|
||||
const DEDICATED_PROXY = process.env.DEDICATED_PROXY || '';
|
||||
if (DEDICATED_PROXY !== '' && DEDICATED_PROXY !== accessTokenPayload.customer_id) {
|
||||
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
|
||||
}
|
||||
|
||||
// Bloquear o customer de acesso o maestro publico
|
||||
const hasNetworkPolicyModule = accessTokenPayload.customer_modules.includes('network-policy');
|
||||
if (hasNetworkPolicyModule && DEDICATED_PROXY === '') {
|
||||
throw new ForbiddenException(ErrorCodes.AUTH.FORBIDDEN);
|
||||
}
|
||||
|
||||
request.accessTokenPayload = accessTokenPayload;
|
||||
request.user = {
|
||||
user_id: accessTokenPayload.user_id,
|
||||
@@ -121,6 +156,7 @@ export class AuthenticationGuard
|
||||
customer_id: accessTokenPayload.customer_id,
|
||||
customer_name: accessTokenPayload.customer_name,
|
||||
customer_tier: accessTokenPayload.customer_tier,
|
||||
customer_modules: accessTokenPayload.customer_modules,
|
||||
access_token: accessToken,
|
||||
};
|
||||
// TODO: for backwards compatibility. remove in the future
|
||||
|
||||
@@ -37,11 +37,12 @@ export interface PermissionsObjectGroup {
|
||||
/*export const Permissions: {
|
||||
[P in keyof any]: { [Q in keyof any]: Permission };
|
||||
} = {*/
|
||||
export const PERMISSIONS = {
|
||||
export const PERMISSIONS_GROUPS = {
|
||||
AUTH: {
|
||||
title: {
|
||||
'pt-br': 'Autenticação',
|
||||
'en-us': 'Authentication',
|
||||
'es-es': 'Authentication',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
@@ -51,15 +52,26 @@ export const PERMISSIONS = {
|
||||
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: {
|
||||
title: {
|
||||
'pt-br': 'Coletar | Pipelines',
|
||||
'en-us': 'Collect | Pipelines',
|
||||
'es-es': 'Colecta | Pipelines',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
@@ -69,6 +81,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Criar pipelines',
|
||||
'en-us': 'Create pipelines',
|
||||
'es-es': 'Crear pipelines',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
@@ -78,6 +91,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Buscar pipelines',
|
||||
'en-us': 'Get pipelines',
|
||||
'es-es': 'Obtener pipelines',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
@@ -87,6 +101,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Atualizar pipeline',
|
||||
'en-us': 'Update pipeline',
|
||||
'es-es': 'Actualizar pipeline',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
@@ -96,69 +111,84 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Excluir pipeline',
|
||||
'en-us': 'Delete pipeline',
|
||||
'es-es': 'Eliminar pipeline',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
INPUT: {
|
||||
CONNECTION: {
|
||||
title: {
|
||||
'pt-br': 'Coletar | Conexões',
|
||||
'en-us': 'Collect | Connections',
|
||||
'pt-br': 'Coletar | Fontes de dados',
|
||||
'en-us': 'Collect | Sources',
|
||||
'es-es': 'Colecta | Sources',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
seqid: 21,
|
||||
claim: 'POST /inputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
seqid: 37,
|
||||
claim: 'connection:create',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Criar conexões',
|
||||
'en-us': 'Create connections',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
seqid: 9,
|
||||
claim: 'GET /inputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Buscar conexões',
|
||||
'en-us': 'Get connections',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 27,
|
||||
claim: 'PUT /inputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Atualizar conexões',
|
||||
'en-us': 'Create connections',
|
||||
'pt-br': 'Cadastrar Fonte',
|
||||
'en-us': 'Create Source',
|
||||
'es-es': 'Crear Source',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
seqid: 3,
|
||||
claim: 'DELETE /inputs',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
seqid: 38,
|
||||
claim: 'connection:delete',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Excluir conexões',
|
||||
'en-us': 'Delete connections',
|
||||
'pt-br': 'Excluir Fonte',
|
||||
'en-us': 'Remove Source',
|
||||
'es-es': 'Eliminar Source',
|
||||
},
|
||||
},
|
||||
GET_ENTITIES: {
|
||||
seqid: 10,
|
||||
claim: 'GET /inputs/available-entities',
|
||||
UPDATE: {
|
||||
seqid: 39,
|
||||
claim: 'connection:update',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Excluir conexões',
|
||||
'en-us': 'Delete connections',
|
||||
'pt-br': 'Editar Fonte',
|
||||
'en-us': 'Edit Source',
|
||||
'es-es': 'Editar Source',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
NETWORK_CONFIG: {
|
||||
title: {
|
||||
'pt-br': 'Coletar | Redes',
|
||||
'en-us': 'Collect | Network',
|
||||
'es-es': 'Colecta | Network',
|
||||
},
|
||||
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: {
|
||||
title: {
|
||||
'pt-br': 'Output',
|
||||
'en-us': 'Output',
|
||||
'es-es': 'Output',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
@@ -168,6 +198,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Criar outputs',
|
||||
'en-us': 'Create outputs',
|
||||
'es-es': 'Crear outputs',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
@@ -177,6 +208,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Buscar outputs',
|
||||
'en-us': 'Get outputs',
|
||||
'es-es': 'Obtener outputs',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
@@ -186,6 +218,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Editar outputs',
|
||||
'en-us': 'Edit outputs',
|
||||
'es-es': 'Editar outputs',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
@@ -195,15 +228,16 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Excluir outputs',
|
||||
'en-us': 'Remove outputs',
|
||||
'es-es': 'Eliminar outputs',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
TRANSFORMATIONS: {
|
||||
title: {
|
||||
'pt-br': 'Micro-transformações',
|
||||
'en-us': 'Micro-transformations',
|
||||
'es-es': 'Micro-transformaciones',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
@@ -213,6 +247,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Criar micro-transformações',
|
||||
'en-us': 'Create micro-transformations',
|
||||
'es-es': 'Crear micro-transformaciones',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
@@ -222,6 +257,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Buscar micro-transformações',
|
||||
'en-us': 'Get micro-transformations',
|
||||
'es-es': 'Obtener micro-transformaciones',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
@@ -231,6 +267,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Editar micro-transformações',
|
||||
'en-us': 'Edit micro-transformations',
|
||||
'es-es': 'Editar micro-transformaciones',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
@@ -240,15 +277,16 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Excluir micro-transformações',
|
||||
'en-us': 'Remove micro-transformations',
|
||||
'es-es': 'Eliminar micro-transformaciones',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
CATALOG: {
|
||||
title: {
|
||||
'pt-br': 'Explorar | Catálogo',
|
||||
'en-us': 'Explore | Catalog',
|
||||
'es-es': 'Explorar | Catalogar',
|
||||
},
|
||||
permissions: {
|
||||
GET: {
|
||||
@@ -256,44 +294,88 @@ export const PERMISSIONS = {
|
||||
claim: 'GET /catalog',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Buscar data assets',
|
||||
'en-us': 'Search for data assets',
|
||||
'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: 'POST /catalog',
|
||||
claim: 'catalog:create',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Criar atributos no catálogo',
|
||||
'en-us': 'Create attributes',
|
||||
'pt-br': 'Criar Ativos',
|
||||
'en-us': 'Create Assets',
|
||||
'es-es': 'Crear Activos',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
seqid: 25,
|
||||
claim: 'PUT /catalog',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
claim: 'catalog:edit',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Editar atributos no catálogo',
|
||||
'en-us': 'Edit attributes',
|
||||
'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: 'DELETE /catalog',
|
||||
claim: 'catalog:delete',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Excluir Ativos do catálogo',
|
||||
'en-us': 'Remove Assets from the catalog',
|
||||
'es-es': 'Eliminar Activos del catálogo',
|
||||
},
|
||||
},
|
||||
DATA_MANAGER: {
|
||||
seqid: 42,
|
||||
claim: 'catalog:data_manager',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Excluir atributos no catálogo',
|
||||
'en-us': 'Remove attributes',
|
||||
'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.',
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
EMBED: {
|
||||
title: {
|
||||
'pt-br': 'Analisar | Incorporação',
|
||||
'en-us': 'Analyze | Embedding',
|
||||
'es-es': 'Analizar | Incorporación',
|
||||
},
|
||||
permissions: {
|
||||
EMBED_ANALYTICS: {
|
||||
seqid: 44,
|
||||
claim: 'catalog:embed',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
CONNECTORS: {
|
||||
title: {
|
||||
'pt-br': 'Conectores',
|
||||
'en-us': 'Connectors',
|
||||
'es-es': 'Conectores',
|
||||
},
|
||||
permissions: {
|
||||
CREATE: {
|
||||
@@ -303,6 +385,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Criar conectores',
|
||||
'en-us': 'Create connectors',
|
||||
'es-es': 'Crear conectores',
|
||||
},
|
||||
},
|
||||
GET: {
|
||||
@@ -312,6 +395,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Buscar conectores',
|
||||
'en-us': 'Get connectors',
|
||||
'es-es': 'Obtener conectores',
|
||||
},
|
||||
},
|
||||
UPDATE: {
|
||||
@@ -321,6 +405,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Editar conectores',
|
||||
'en-us': 'Edit connectors',
|
||||
'es-es': 'Editar conectores',
|
||||
},
|
||||
},
|
||||
DELETE: {
|
||||
@@ -330,15 +415,16 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Excluir conectores',
|
||||
'en-us': 'Remove connectors',
|
||||
'es-es': 'Eliminar conectores',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SNOWFLAKE: {
|
||||
title: {
|
||||
'pt-br': 'Explorar | Consolidar',
|
||||
'en-us': 'Explore | Data consolidation',
|
||||
'es-es': 'Explorar | Consolidación de datos',
|
||||
},
|
||||
permissions: {
|
||||
OPEN: {
|
||||
@@ -348,62 +434,75 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Acessar Snowflake',
|
||||
'en-us': 'Access Snowflake',
|
||||
'es-es': 'Acceso Snowflake',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
ZENDESK: {
|
||||
title: {
|
||||
'pt-br': 'Zendesk',
|
||||
'en-us': 'Zendesk',
|
||||
'es-es': 'Zendesk',
|
||||
},
|
||||
permissions: {
|
||||
OPEN: {
|
||||
seqid: 16,
|
||||
claim: 'GET /zendesk',
|
||||
claim: 'support-chat:access',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
name: {
|
||||
'pt-br': 'Acessar Zendesk',
|
||||
'en-us': 'Access Zendesk',
|
||||
'pt-br': 'Acessar chat de suporte',
|
||||
'en-us': 'Access support chat',
|
||||
'es-es': 'Acceder al chat de soporte',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
METABASE: {
|
||||
DATAVIZ: {
|
||||
title: {
|
||||
'pt-br': 'Analisar | Visualização',
|
||||
'en-us': 'Analyze | Visualization',
|
||||
'es-es': 'Analizar | Visualización',
|
||||
},
|
||||
permissions: {
|
||||
OPEN: {
|
||||
METABASE: {
|
||||
seqid: 11,
|
||||
claim: 'GET /metabase',
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar Metabase',
|
||||
'en-us': 'Access Metabase',
|
||||
'es-es': 'Acceso Metabase',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
ORCHEST: {
|
||||
INTELLIGENCE: {
|
||||
title: {
|
||||
'pt-br': 'Analisar | Inteligência',
|
||||
'en-us': 'Analyze | Intelligence',
|
||||
'es-es': 'Analizar | Inteligencia',
|
||||
},
|
||||
permissions: {
|
||||
INTELLIGENCE: {
|
||||
seqid: 31,
|
||||
claim: 'intelligence:open',
|
||||
usage: PermissionUsages.INTERNAL,
|
||||
usage: PermissionUsages.PUBLIC,
|
||||
name: {
|
||||
'pt-br': 'Acessar Orchest',
|
||||
'en-us': 'Access Orchest',
|
||||
'pt-br': 'Acessar Módulo de Inteligência',
|
||||
'en-us': 'Access Intelligence Module',
|
||||
'es-s': 'Acceder Módulo de Inteligencia',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MODULES: {
|
||||
title: {
|
||||
'pt-br': 'Módulos da Dadosfera',
|
||||
'en-us': 'Dadosfera Modules',
|
||||
'es-es': 'Módulos de la Dadosfera',
|
||||
},
|
||||
permissions: {
|
||||
APP_BUILDER: {
|
||||
seqid: 32,
|
||||
claim: 'app-builder:open',
|
||||
@@ -411,6 +510,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Acessar App Builder',
|
||||
'en-us': 'Access App Builder',
|
||||
'es-es': 'Acceder App Builder',
|
||||
},
|
||||
},
|
||||
MACHINE_LEARNING: {
|
||||
@@ -420,6 +520,7 @@ export const PERMISSIONS = {
|
||||
name: {
|
||||
'pt-br': 'Acessar Machine Learning',
|
||||
'en-us': 'Access Machine Learning',
|
||||
'es-es': 'Acceder Machine Learning',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -428,6 +529,7 @@ export const PERMISSIONS = {
|
||||
title: {
|
||||
'pt-br': 'Usuários',
|
||||
'en-us': 'Users',
|
||||
'es-es': 'Usuarios',
|
||||
},
|
||||
permissions: {
|
||||
ADMIN: {
|
||||
@@ -437,21 +539,127 @@ export const PERMISSIONS = {
|
||||
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_KEYS = {
|
||||
LOG_DASHBOARD: 'logs-dashboard',
|
||||
ACCESS_DASHBOARD: 'access-dashboard',
|
||||
DANGER_ZONE: 'danger-zone',
|
||||
PII: 'pii',
|
||||
EMBED: 'embedded-analytics',
|
||||
EMBED_ASSIGNED: 'embed-assigned',
|
||||
}
|
||||
|
||||
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) =>
|
||||
const seqids = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||
Object.values(namespace.permissions).map(({ seqid }) => seqid),
|
||||
);
|
||||
const claims = Object.values(PERMISSIONS).flatMap((namespace) =>
|
||||
const claims = Object.values(PERMISSIONS_GROUPS).flatMap((namespace) =>
|
||||
Object.values(namespace.permissions).map(({ claim }) => claim),
|
||||
);
|
||||
Object.values(PERMISSIONS).map((namespace) =>
|
||||
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`);
|
||||
@@ -462,4 +670,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,26 +0,0 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
import { ProtoPackages, ProtoPaths } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||
|
||||
export class DucClient {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.DUC_URL,
|
||||
package: ProtoPackages.WritePackage,
|
||||
// credentials: credentials.createSsl(),
|
||||
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/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: DadosferaLogger;
|
||||
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,178 +0,0 @@
|
||||
import { Controller, Inject, OnModuleInit } from '@nestjs/common';
|
||||
import { ClientGrpc, Payload } from '@nestjs/microservices';
|
||||
import { DadosferaLogger } from '@dadosfera/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: DadosferaLogger;
|
||||
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,24 +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/dadosfera-logs';
|
||||
|
||||
export class PipelinesClientService implements OnModuleInit {
|
||||
private pipelineService: PipelinesServiceInterface;
|
||||
logger: DadosferaLogger;
|
||||
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/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: DadosferaLogger;
|
||||
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,49 @@
|
||||
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 RequireModule(
|
||||
key: string
|
||||
) {
|
||||
return createAuthenticatedDecorator((_, user: RequestUser) =>
|
||||
user.customer_modules.some(module => module === key),
|
||||
);
|
||||
}
|
||||
|
||||
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,28 @@
|
||||
import { type CustomDecorator } from '@nestjs/common';
|
||||
|
||||
// implementation copied from SetMetadata, but tweaked to get existing values
|
||||
// of the metadataKey and accumulate it with the new metadataValue
|
||||
export function SetMultipleMetadata(
|
||||
metadataKey,
|
||||
metadataValue,
|
||||
): CustomDecorator {
|
||||
const decoratorFactory: CustomDecorator = (target, key?, descriptor?) => {
|
||||
// .start: tweak
|
||||
// descriptor?.value = function decorator; target = class decorator
|
||||
const accumulatedVal =
|
||||
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
|
||||
accumulatedVal.push(metadataValue);
|
||||
// .end: tweak
|
||||
|
||||
if (descriptor) {
|
||||
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
|
||||
return target;
|
||||
};
|
||||
|
||||
decoratorFactory.KEY = metadataKey;
|
||||
return decoratorFactory;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { applyDecorators } from '@nestjs/common';
|
||||
import { ApiExcludeController, ApiExcludeEndpoint } from '@nestjs/swagger';
|
||||
|
||||
export function ApiInternalOnlyEndpoint() {
|
||||
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||
return applyDecorators(ApiExcludeEndpoint());
|
||||
return () => null;
|
||||
}
|
||||
export function ApiInternalOnlyController() {
|
||||
if (process.env.INTERNAL_SWAGGER !== 'true')
|
||||
return applyDecorators(ApiExcludeController());
|
||||
return () => null;
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import request from 'supertest';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { AuthenticationGuard } from './authentication.guard';
|
||||
import { Controller, Get, HttpStatus, INestApplication } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import request from 'supertest';
|
||||
import { AuthenticationGuard } from '../authentication/authentication.guard';
|
||||
import { PERMISSIONS_GROUPS } from '../authentication/permissions.enum';
|
||||
import { AuthClientService } from '../modules/auth/auth.service';
|
||||
|
||||
import ErrorCodes from '../utils/errorCodes';
|
||||
import { User } from './user.decorator';
|
||||
import { PERMISSIONS } from './permissions.enum';
|
||||
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
|
||||
|
||||
const logger = {
|
||||
info: (...args) => args,
|
||||
@@ -49,12 +47,13 @@ describe('user.decorator', () => {
|
||||
const fakeUserPayload = {
|
||||
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
|
||||
username: 'super.admin',
|
||||
permissions: [PERMISSIONS.METABASE.permissions.OPEN].map(
|
||||
permissions: [PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE].map(
|
||||
({ seqid }) => seqid,
|
||||
),
|
||||
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
|
||||
customer_name: 'dadosfera',
|
||||
customer_tier: 'BASIC',
|
||||
customer_modules: [],
|
||||
access_token: '',
|
||||
};
|
||||
|
||||
@@ -77,6 +76,12 @@ describe('user.decorator', () => {
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthenticationGuard,
|
||||
},
|
||||
{
|
||||
provide: ApiKeyService,
|
||||
useValue: {
|
||||
get: () => Promise.resolve(null)
|
||||
}
|
||||
}
|
||||
],
|
||||
controllers: [UserController],
|
||||
}).compile();
|
||||
@@ -178,5 +183,5 @@ describe('user.decorator', () => {
|
||||
|
||||
const token = CreateToken();
|
||||
fakeUserPayload.access_token = token;
|
||||
UserTest(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';
|
||||
@@ -11,6 +11,7 @@ export interface RequestUser {
|
||||
customer_name: string;
|
||||
customer_tier: string;
|
||||
access_token: string;
|
||||
customer_modules: string[];
|
||||
}
|
||||
|
||||
export const User: (options?: { required?: boolean }) => ParameterDecorator =
|
||||
@@ -20,6 +21,5 @@ export const User: (options?: { required?: boolean }) => ParameterDecorator =
|
||||
if (!request.user && options?.required) {
|
||||
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return request.user;
|
||||
});
|
||||
@@ -1,16 +1,31 @@
|
||||
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
|
||||
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>();
|
||||
|
||||
// return this exception directly if is already of type ErrorBuilder,
|
||||
// else transform it using the ErrorBuilder
|
||||
if (exception instanceof HttpException) {
|
||||
return response
|
||||
.status(exception.getStatus())
|
||||
.json(exception.getResponse());
|
||||
}
|
||||
|
||||
const err =
|
||||
exception.constructor.name === ErrorBuilder.name
|
||||
? exception
|
||||
|
||||
+60
-18
@@ -3,10 +3,14 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import helmet from 'helmet';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import documentEmpty from '../swagger_empty.json';
|
||||
import { json, urlencoded } from 'express';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
async function bootstrap() {
|
||||
DadosferaLogger.setupLogger({
|
||||
serviceName: 'maestro',
|
||||
@@ -21,31 +25,69 @@ async function bootstrap() {
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204,
|
||||
credentials: true
|
||||
},
|
||||
});
|
||||
|
||||
app.use(helmet());
|
||||
app.use(cookieParser(process.env.COOKIE_SECRET));
|
||||
if (process.env.ENV === 'prd') {
|
||||
app.use('/catalog/register-dataset', json({ limit: '10mb' }));
|
||||
app.use('/catalog/register-dataset', urlencoded({ extended: true, limit: '10mb' }));
|
||||
}
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Maestro')
|
||||
configureSwagger(app);
|
||||
await app.listen(3333);
|
||||
if (process.env.KILL_AFTER_START) await app.close();
|
||||
}
|
||||
|
||||
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()
|
||||
.addServer(
|
||||
process.env.ENV === 'local'
|
||||
? 'http://127.0.0.1:3333'
|
||||
: 'https://maestro.dev.dadosfera.ai',
|
||||
)
|
||||
.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,68 @@
|
||||
import { Controller, Get, Post, Body, Param, Delete, UseFilters, Inject } from '@nestjs/common';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
import { CreateApiKeyDto, CreateApiKeyResponseDto, ApiKeyBaseResponseDto } from './dto/api-key.dto';
|
||||
import { Authenticated } from 'src/decorators/authentication.decorator';
|
||||
import { ApiHeaders, ApiTags, ApiResponse } from '@nestjs/swagger';
|
||||
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
@Controller('api-key')
|
||||
@Authenticated()
|
||||
@ApiTags('ApiKey')
|
||||
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
export class ApiKeyController {
|
||||
logger: DadosferaLogger;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private readonly apiKeyService: ApiKeyService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiResponse({ type: CreateApiKeyResponseDto })
|
||||
async create(@Body() createApiKeyDto: CreateApiKeyDto, @User() user: RequestUser): Promise<CreateApiKeyResponseDto> {
|
||||
this.logger.info('POST /api-key', {
|
||||
permissions: createApiKeyDto.permissions,
|
||||
method: 'create'
|
||||
});
|
||||
const result = await this.apiKeyService.create(createApiKeyDto, user);
|
||||
this.logger.info('POST /api-key success', {
|
||||
id: result.id,
|
||||
method: 'create'
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiResponse({ type: [ApiKeyBaseResponseDto] })
|
||||
async findAll(@User() user: RequestUser): Promise<ApiKeyBaseResponseDto[]> {
|
||||
this.logger.info('GET /api-key', {
|
||||
method: 'findAll'
|
||||
});
|
||||
const result = await this.apiKeyService.findAll(user);
|
||||
this.logger.info('GET /api-key success', {
|
||||
count: result.length,
|
||||
method: 'findAll'
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string, @User() user: RequestUser): Promise<void> {
|
||||
this.logger.info('DELETE /api-key/:id', {
|
||||
id,
|
||||
method: 'remove'
|
||||
});
|
||||
await this.apiKeyService.remove(id, user);
|
||||
this.logger.info('DELETE /api-key/:id success', {
|
||||
id,
|
||||
method: 'remove'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
import { ApiKeyController } from './api-key.controller';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { DucClient } from '../duc/client.config';
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||
|
||||
const ducClient = new DucClient();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.register([ducClient.providerOptions])
|
||||
],
|
||||
controllers: [ApiKeyController],
|
||||
providers: [ApiKeyService, DadosferaLogger],
|
||||
exports: [ApiKeyService]
|
||||
})
|
||||
export class ApiKeyModule {}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Injectable, Inject, OnModuleInit } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { CreateApiKeyDto, CreateApiKeyResponseDto, ApiKeyBaseResponseDto } from './dto/api-key.dto';
|
||||
import { RequestUser } from 'src/decorators/user.decorator';
|
||||
import { DucClient } from '../duc/client.config';
|
||||
import { ApiKeyWriteProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||
import { PackTheMetadata } from 'src/utils/PackTheMetadata';
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyService implements OnModuleInit {
|
||||
private apiKeyService: ApiKeyWriteProtoService;
|
||||
|
||||
constructor(
|
||||
@Inject(DucClient.name) private readonly client: ClientGrpc,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.apiKeyService = this.client.getService<ApiKeyWriteProtoService>(ProtoServices.ApiKeyWriteProtoService);
|
||||
}
|
||||
|
||||
create(createApiKeyDto: CreateApiKeyDto, user: RequestUser): Promise<CreateApiKeyResponseDto> {
|
||||
const metadata = PackTheMetadata(user);
|
||||
|
||||
return lastValueFrom(this.apiKeyService.CreateApiKey({
|
||||
permissions: createApiKeyDto.permissions
|
||||
}, metadata));
|
||||
}
|
||||
|
||||
async findAll(user: RequestUser): Promise<ApiKeyBaseResponseDto[]> {
|
||||
const metadata = PackTheMetadata(user);
|
||||
console.log(metadata)
|
||||
|
||||
const data = await lastValueFrom(this.apiKeyService.ListApiKeys({}, metadata));
|
||||
return data.api_keys;
|
||||
}
|
||||
|
||||
async remove(id: string, user: RequestUser) {
|
||||
const metadata = PackTheMetadata(user);
|
||||
|
||||
await lastValueFrom(this.apiKeyService.DeleteApiKey({ id }, metadata));
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
const metadata = PackTheMetadata({});
|
||||
|
||||
return await lastValueFrom(this.apiKeyService.GetApiKey({ key }, metadata));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNumber } from 'class-validator';
|
||||
|
||||
export class PermissionDto {
|
||||
@ApiProperty({ type: Number })
|
||||
id: number;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ApiKeyBaseResponseDto {
|
||||
@ApiProperty({ type: String, format: 'uuid' })
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
key_mask: string;
|
||||
|
||||
@ApiProperty({ type: [PermissionDto] })
|
||||
permissions: PermissionDto[];
|
||||
|
||||
@ApiProperty({ type: String, format: 'date-time' })
|
||||
created_at: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
created_by: string;
|
||||
}
|
||||
|
||||
export class CreateApiKeyResponseDto extends ApiKeyBaseResponseDto {
|
||||
@ApiProperty({ type: String })
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class CreateApiKeyDto {
|
||||
@ApiProperty({ type: [Number], description: 'Array of permission IDs' })
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
permissions: number[];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Controller, Post, Body, Put, Get} from '@nestjs/common';
|
||||
import { AssignService } from './assign.service';
|
||||
import { CreateAssignDto } from './dto/create-assign.dto';
|
||||
import { Authenticated, RequireModule, RequireSomePermission } from 'src/decorators/authentication.decorator';
|
||||
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||
import { DADOSFERA_MODULES_KEYS, PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||
import { PackTheMetadata } from 'src/utils/PackTheMetadata';
|
||||
|
||||
@Controller('assign')
|
||||
@Authenticated()
|
||||
export class AssignController {
|
||||
constructor(private readonly assignService: AssignService) {}
|
||||
|
||||
@Put('/public-key')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.USERS.permissions.ADMIN
|
||||
)
|
||||
@RequireModule(DADOSFERA_MODULES_KEYS.EMBED_ASSIGNED)
|
||||
create(@Body() createAssignDto: CreateAssignDto, @User() user: RequestUser) {
|
||||
const metadata = PackTheMetadata(user);
|
||||
return this.assignService.create(createAssignDto, metadata);
|
||||
}
|
||||
|
||||
@Get('/public-key')
|
||||
@RequireSomePermission(
|
||||
PERMISSIONS_GROUPS.USERS.permissions.ADMIN
|
||||
)
|
||||
@RequireModule(DADOSFERA_MODULES_KEYS.EMBED_ASSIGNED)
|
||||
async get(@User() user: RequestUser) {
|
||||
const metadata = PackTheMetadata(user);
|
||||
return await this.assignService.get(metadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AssignService } from './assign.service';
|
||||
import { AssignController } from './assign.controller';
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||
import { ClientsModule } from '@nestjs/microservices';
|
||||
import { DucClient } from '../duc/client.config';
|
||||
|
||||
const client = new DucClient();
|
||||
|
||||
@Module({
|
||||
imports: [ClientsModule.register([client.providerOptions])],
|
||||
controllers: [AssignController],
|
||||
providers: [AssignService, DadosferaLogger]
|
||||
})
|
||||
export class AssignModule {}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { CreateAssignDto } from './dto/create-assign.dto';
|
||||
import { Metadata } from '@grpc/grpc-js';
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { DucClient } from 'src/modules/duc/client.config';
|
||||
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||
import { lastValueFrom } from 'rxjs';import { AssingProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
|
||||
@Injectable()
|
||||
export class AssignService implements OnModuleInit {
|
||||
|
||||
ducService: AssingProtoService;
|
||||
logger: DadosferaLogger;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.ducService =this.grpcClient.getService<AssingProtoService>(
|
||||
ProtoServices.AssingProtoService,
|
||||
);
|
||||
}
|
||||
|
||||
async create(createAssignDto: CreateAssignDto, metadata: Metadata) {
|
||||
const data = await lastValueFrom(this.ducService.CreateOrUpdateAssignPublicKey(createAssignDto, metadata))
|
||||
return data;
|
||||
}
|
||||
|
||||
async get(metadata: Metadata) {
|
||||
return await lastValueFrom(this.ducService.GetAssignPublicKey({}, metadata))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class CreateAssignDto {
|
||||
publicKey: string;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Auth
|
||||
|
||||
## SSO/oAuth
|
||||
|
||||
### Strategy
|
||||
We are using [PassportJs](https://www.passportjs.org/) to handle oAuth authentications.
|
||||
|
||||
When the client (front end) makes a `GET /auth/oauth/{strategy}` Passport automatically redirects the user to the `strategy` login page. To do that we must configure and use a **Passport Strategy**. We must also have a callback route, conventionally `GET /auth/oauth/{strategy}/callback`, so the oAuth app can report the status of the user's login.
|
||||
|
||||
- If the oAuth is successfull we call DUC's `AuthOauthSignIn` request that gets the tokens from Cognito and saves them on cache temporarily under a key we call `session`. Duc returns that `session` to maestro which then redirects the user to our app login page with that `session` as a query param.
|
||||
|
||||
- If the oAuth login is not successfull for some reason or the user **does not** exist on DUC's database we redirect the user to our login page with an `error` and `error_description` as query params.
|
||||
|
||||
### Routes
|
||||
|
||||
So in order to have an SSO login, besides configuring the Strategy, we must have two routes for each Strategy, like in the example below:
|
||||
|
||||
```ts
|
||||
@Get('oauth/google')
|
||||
@UseGuards(AuthGuard('google-login'))
|
||||
googleOauth() {
|
||||
this.logger.info('/oauth/google');
|
||||
return true;
|
||||
}
|
||||
|
||||
@Get('oauth/google/callback')
|
||||
@UseGuards(AuthGuard('google-login'))
|
||||
@Redirect()
|
||||
async googleOauthCallback(@Req() req) {
|
||||
const { url, email, token, language = 'pt-br' } = await this.callback(req);
|
||||
if (url.searchParams.get('error')) {
|
||||
this.logger.error('/oauth/google - ERROR');
|
||||
return { url: url.href };
|
||||
}
|
||||
// ... Rest of the logic
|
||||
return { url: url.href };
|
||||
}
|
||||
```
|
||||
@@ -7,29 +7,68 @@ import {
|
||||
HttpStatus,
|
||||
Inject,
|
||||
UseFilters,
|
||||
Get,
|
||||
UseGuards,
|
||||
Redirect,
|
||||
Req,
|
||||
Param,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
AuthSignInRequest,
|
||||
AuthRefreshAccessTokenRequest,
|
||||
ApiHeaders,
|
||||
ApiOkResponse,
|
||||
ApiSecurity,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import {
|
||||
AuthChangePasswordRequest,
|
||||
AuthResetPasswordRequest,
|
||||
AuthVerifyResetPasswordCodeRequest,
|
||||
AuthConfirmResetPasswordRequest,
|
||||
AuthEnableTotpMfaRequest,
|
||||
AuthDisableTotpMfaRequest,
|
||||
AuthVerifyTotpMfaRequest,
|
||||
AuthVerifyTotpMfaRequest
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
|
||||
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,
|
||||
BulkEditRequest,
|
||||
} from './dtos/login';
|
||||
import { PackTheMetadata } from 'src/utils/PackTheMetadata';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Request, Response } from 'express';
|
||||
import ErrorCodes, { OauthErrors } from 'src/utils/errorCodes';
|
||||
import jwt, { JwtPayload } from 'jsonwebtoken';
|
||||
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||
import { Language } from 'src/decorators/language.decorator';
|
||||
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
|
||||
import { Cookie } from 'express-session';
|
||||
|
||||
type CookiesValues = {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
userId?: string
|
||||
}
|
||||
|
||||
@ApiTags('Auth')
|
||||
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
logger: DadosferaLogger;
|
||||
redirectUrl: string;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
@@ -37,26 +76,131 @@ export class AuthController {
|
||||
private authClient: AuthClientService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
|
||||
switch (process.env.ENV) {
|
||||
case 'stg':
|
||||
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/auth/login`;
|
||||
break;
|
||||
case 'prd':
|
||||
this.redirectUrl = `https://app.dadosfera.ai/auth/login`;
|
||||
break;
|
||||
default:
|
||||
this.redirectUrl = `http://localhost:4200/auth/login`;
|
||||
}
|
||||
}
|
||||
|
||||
@Post('sign-in')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async signIn(@Body() { username, password, totp }: AuthSignInRequest) {
|
||||
this.logger.info('/auth - SignIn');
|
||||
async signIn(
|
||||
@Body() { username, password, totp }: AuthSignInReq,
|
||||
@Language() language: LanguageEnum,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
try {
|
||||
this.logger.info('/auth - SignIn');
|
||||
const metadata = PackTheMetadata({ language });
|
||||
this.logger.info('metadata: ' + JSON.stringify(metadata.toJSON()));
|
||||
const data = await this.authClient.signIn({ username, password, totp }, metadata);
|
||||
|
||||
if (data.tokens) {
|
||||
this.addTokenInCookie(res, {
|
||||
accessToken: data.tokens.accessToken,
|
||||
refreshToken: data.tokens.refreshToken,
|
||||
userId: data.user.id
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return res.send(data);
|
||||
} catch (error) {
|
||||
this.logger.error('/auth - SignIn - ERROR', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Post('sign-out')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async signOut(
|
||||
@Language() language: LanguageEnum,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
try {
|
||||
this.logger.info('/auth - SignOut');
|
||||
const exp = 1000 * 60 * 3;
|
||||
|
||||
res.cookie('ddf-auth', '', {
|
||||
domain: 'dadosfera.local',
|
||||
maxAge: Date.now() - exp,
|
||||
expires: new Date(),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'none', // Necessário para cookies em requisições cross-site
|
||||
});
|
||||
|
||||
res.cookie('ddf-refresh-auth', '', {
|
||||
domain: 'dadosfera.local',
|
||||
maxAge: Date.now() - exp,
|
||||
expires: new Date(),
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'none', // Necessário para cookies em requisições cross-site
|
||||
});
|
||||
|
||||
this.logger.info('Clean cookie sessions');
|
||||
|
||||
return res.send();
|
||||
} catch (error) {
|
||||
this.logger.error('/auth - SignIn - ERROR', error);
|
||||
}
|
||||
|
||||
return this.authClient.signIn({ username, password, totp });
|
||||
}
|
||||
|
||||
@Post('refresh-access-token')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOkResponse({ type: AuthRefreshAccessTokenRes })
|
||||
async refreshAccessToken(
|
||||
@Body() { refreshToken }: AuthRefreshAccessTokenRequest,
|
||||
@Body() body: AuthRefreshAccessTokenReq,
|
||||
@Language() language: LanguageEnum,
|
||||
@Headers('origin') origin: string,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
this.logger.info('/auth - RefreshAccessToken');
|
||||
const frontHost = origin.replace(/^https?:\/\//, '');
|
||||
const { refreshToken, userId } = body;
|
||||
|
||||
return this.authClient.refreshAccessToken({ refreshToken });
|
||||
const metadata = PackTheMetadata({
|
||||
language,
|
||||
custom_host: frontHost,
|
||||
});
|
||||
|
||||
const data = await this.authClient.refreshAccessToken({ refreshToken, userId }, metadata);
|
||||
|
||||
this.addTokenInCookie(res, {
|
||||
accessToken: data.accessToken,
|
||||
userId
|
||||
});
|
||||
|
||||
return res.send(data);
|
||||
}
|
||||
|
||||
@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(
|
||||
@@ -75,16 +219,24 @@ export class AuthController {
|
||||
});
|
||||
}
|
||||
|
||||
@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(
|
||||
@@ -97,20 +249,29 @@ export class AuthController {
|
||||
return this.authClient.verifyResetPasswordCode({ username, code });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('confirm-reset-password')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async confirmResetPassword(@Body() body: AuthConfirmResetPasswordRequest) {
|
||||
async confirmResetPassword(
|
||||
@Body() body: AuthConfirmResetPasswordRequest,
|
||||
@Headers('origin') origin: string,
|
||||
) {
|
||||
this.logger.info('/auth - confirm-reset-password');
|
||||
|
||||
const frontHost = origin.replace(/^https?:\/\//, '');
|
||||
const metadata = PackTheMetadata({ custom_host: frontHost });
|
||||
const { username, code, newPassword } = body;
|
||||
|
||||
return this.authClient.confirmResetPassword({
|
||||
username,
|
||||
code,
|
||||
newPassword,
|
||||
});
|
||||
return this.authClient.confirmResetPassword(
|
||||
{
|
||||
username,
|
||||
code,
|
||||
newPassword,
|
||||
},
|
||||
metadata,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('enable-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async enableTotpMFA(
|
||||
@@ -125,6 +286,7 @@ export class AuthController {
|
||||
return this.authClient.enableTotpMFA({ accessToken, password });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('disable-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async disableTotpMFA(
|
||||
@@ -139,6 +301,7 @@ export class AuthController {
|
||||
return this.authClient.disableTotpMFA({ accessToken, password });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('dismiss-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async dismissTotpMFA(@Headers() headers) {
|
||||
@@ -149,6 +312,7 @@ export class AuthController {
|
||||
return this.authClient.dismissTotpMFA({ accessToken });
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('verify-totp')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async verifyTotp(@Body() body: AuthVerifyTotpMfaRequest, @Headers() headers) {
|
||||
@@ -159,4 +323,284 @@ export class AuthController {
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('users/block')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async blockUsers(
|
||||
@Language() language: LanguageEnum,
|
||||
@User() user: RequestUser,
|
||||
@Body() body: BulkEditRequest,
|
||||
) {
|
||||
this.logger.info('blockUsers - Starting request');
|
||||
|
||||
try {
|
||||
const metadata = PackTheMetadata(user);
|
||||
|
||||
this.logger.debug('Calling blockUsers service', {
|
||||
metadata: {
|
||||
access_token: metadata.get('access_token'),
|
||||
language: metadata.get('language'),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await this.authClient.blockUsers(body.users, metadata);
|
||||
this.logger.info('blockUsers - Success', { result });
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error('blockUsers - Error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('users/unblock')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async unblockUsers(
|
||||
@Language() language: LanguageEnum,
|
||||
@User() user: RequestUser,
|
||||
@Body() body: BulkEditRequest,
|
||||
) {
|
||||
this.logger.info('unblockUsers');
|
||||
const metadata = PackTheMetadata(user);
|
||||
|
||||
return this.authClient.unblockUsers(body.users, metadata);
|
||||
}
|
||||
|
||||
@ApiInternalOnlyEndpoint()
|
||||
@Post('users/reset')
|
||||
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async resetUsers(
|
||||
@Language() language: LanguageEnum,
|
||||
@User() user: RequestUser,
|
||||
@Body() body: BulkEditRequest,
|
||||
) {
|
||||
this.logger.info('resetUsers');
|
||||
const metadata = PackTheMetadata(user);
|
||||
|
||||
return this.authClient.resetUsers(body.users, metadata);
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
async getMe(@Req() req: Request, @Res() res: Response) {
|
||||
this.logger.info('GET /auth/me ')
|
||||
// Lê cookies
|
||||
const accessToken = req.cookies['ddf-auth'];
|
||||
const userId = req.cookies['ddf-user-id'];
|
||||
|
||||
this.logger.info('Has cookie: ' + Boolean(accessToken))
|
||||
let payload: any;
|
||||
let userInfo: any = {};
|
||||
try {
|
||||
// Decodifica e valida o JWT de acesso
|
||||
const decoded: any = accessToken && jwt.decode(accessToken, { complete: true });
|
||||
if (!decoded) throw new Error('Invalid token')
|
||||
const { kid } = decoded.header;
|
||||
// Busca a chave pública
|
||||
const { keys } = await this.authClient.getPublicKeys();
|
||||
const pemValue = keys.find((k) => k.kid === kid)?.pem;
|
||||
if (!pemValue) throw new Error('Public key not found');
|
||||
jwt.verify(accessToken, pemValue);
|
||||
payload = decoded.payload;
|
||||
userInfo = {
|
||||
id: payload.user_id,
|
||||
name: payload.username,
|
||||
customer: {
|
||||
id: payload.customer_id,
|
||||
name: payload.customer_name,
|
||||
tier: payload.customer_tier,
|
||||
}
|
||||
};
|
||||
return res.status(200).json(userInfo);
|
||||
} catch (err) {
|
||||
this.logger.error(err.message);
|
||||
const refreshToken = req.cookies['ddf-refresh-auth'];
|
||||
|
||||
this.logger.info('Token is invalid')
|
||||
this.logger.info('Has Refresh Token: '+ Boolean(refreshToken))
|
||||
// Se access token inválido, tenta refresh
|
||||
if (!refreshToken || !userId) {
|
||||
this.logger.error('Invalid refresh token or customer name');
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
try {
|
||||
// Chama refreshAccessToken
|
||||
const metadata = PackTheMetadata({
|
||||
});
|
||||
this.logger.info('Call Refresh Token')
|
||||
const data = await this.authClient.refreshAccessToken({ refreshToken, userId }, metadata);
|
||||
this.logger.info('Finish Refresh Token')
|
||||
// Retorna novo access token e dados mínimos
|
||||
this.addTokenInCookie(res, {
|
||||
accessToken: data.accessToken,
|
||||
userId
|
||||
});
|
||||
// Decodifica novo token
|
||||
const decoded: any = jwt.decode(data.accessToken, { complete: true });
|
||||
const payload = decoded.payload;
|
||||
userInfo = {
|
||||
id: payload.user_id,
|
||||
name: payload.username,
|
||||
customer: {
|
||||
id: payload.customer_id,
|
||||
name: payload.customer_name,
|
||||
tier: payload.customer_tier,
|
||||
}
|
||||
};
|
||||
return res.status(200).json(userInfo);
|
||||
} catch (refreshErr) {
|
||||
this.logger.error(refreshErr)
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addTokenInCookie(res: Response, data: CookiesValues) {
|
||||
let exp = 1000 * 60 * 5; // 5 minutes
|
||||
|
||||
if (data.accessToken) {
|
||||
const { exp: expiration } = jwt.decode(data.accessToken) as JwtPayload;
|
||||
exp = (expiration - 30) * 1000; // exp em segundos, maxAge em ms
|
||||
|
||||
this.logger.info('Set Cookie ddf-auth')
|
||||
res.cookie('ddf-auth', data.accessToken, {
|
||||
domain: 'stg.dadosfera.ai',
|
||||
maxAge: exp,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'none', // Necessário para cookies em requisições cross-site
|
||||
});
|
||||
}
|
||||
|
||||
if (data.refreshToken) {
|
||||
this.logger.info('Set Cookie ddf-refresh-auth')
|
||||
res.cookie('ddf-refresh-auth', data.refreshToken, {
|
||||
domain: 'stg.dadosfera.ai',
|
||||
maxAge: exp,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'none', // Necessário para cookies em requisições cross-site
|
||||
});
|
||||
}
|
||||
|
||||
if (data.userId) {
|
||||
this.logger.info('Set Cookie ddf-refresh-auth')
|
||||
res.cookie('ddf-user-id', data.userId, {
|
||||
domain: 'stg.dadosfera.ai',
|
||||
maxAge: exp,
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'none', // Necessário para cookies em requisições cross-site
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,20 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthClientService } from './auth.service';
|
||||
|
||||
import { DucClient } from '../../clients/duc/client.config';
|
||||
const ducClient = new DucClient();
|
||||
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([
|
||||
{
|
||||
name: 'DUC_PACKAGE',
|
||||
...ducClient.config(),
|
||||
},
|
||||
]),
|
||||
],
|
||||
imports: [ClientsModule.register([client.providerOptions])],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthClientService, DadosferaLogger],
|
||||
providers: [
|
||||
AuthClientService,
|
||||
DadosferaLogger,
|
||||
GoogleLoginStrategy,
|
||||
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
|
||||
],
|
||||
exports: [AuthClientService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { OnModuleInit, Inject, Injectable } from '@nestjs/common';
|
||||
import { OnModuleInit, Inject, Injectable, ForbiddenException } from '@nestjs/common';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||
import { AuthProtoService as AuthServiceInterface } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import { AuthProtoService as AuthServiceInterface, IdentityProviderProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||
import {
|
||||
AuthSnowflakeSignInRequest,
|
||||
AuthSignInRequest,
|
||||
AuthRefreshAccessTokenRequest,
|
||||
AuthEnableTotpMfaRequest,
|
||||
@@ -16,17 +17,26 @@ import {
|
||||
AuthResetPasswordRequest,
|
||||
AuthVerifyResetPasswordCodeRequest,
|
||||
AuthConfirmResetPasswordRequest,
|
||||
AuthSignInResponse,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
|
||||
import { DucClient } from '../duc/client.config';
|
||||
import { Metadata } from '@grpc/grpc-js';
|
||||
import { BulkEditResponse } from './dtos/login';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class AuthClientService implements OnModuleInit {
|
||||
|
||||
|
||||
logger: DadosferaLogger;
|
||||
|
||||
|
||||
private authService: AuthServiceInterface;
|
||||
private identityProviderService: IdentityProviderProtoService;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject('DUC_PACKAGE') private readonly grpcClient: ClientGrpc,
|
||||
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
@@ -35,6 +45,10 @@ export class AuthClientService implements OnModuleInit {
|
||||
this.authService = this.grpcClient.getService<AuthServiceInterface>(
|
||||
ProtoServices.AuthProtoService,
|
||||
);
|
||||
|
||||
this.identityProviderService = this.grpcClient.getService<IdentityProviderProtoService>(
|
||||
ProtoServices.IdentityProviderProtoService,
|
||||
);
|
||||
}
|
||||
|
||||
async getPublicKeys() {
|
||||
@@ -43,19 +57,65 @@ export class AuthClientService implements OnModuleInit {
|
||||
return lastValueFrom(this.authService.AuthGetPublicKeys({}));
|
||||
}
|
||||
|
||||
async signIn({ username, password, totp }: AuthSignInRequest) {
|
||||
this.logger.info('SignIn');
|
||||
async snowflakeSignIn(input: AuthSnowflakeSignInRequest) {
|
||||
this.logger.info('snowflakeSignIn');
|
||||
|
||||
return lastValueFrom(
|
||||
this.authService.AuthSignIn({ username, password, totp }),
|
||||
);
|
||||
return lastValueFrom(this.authService.AuthSnowflakeSignIn(input));
|
||||
}
|
||||
|
||||
async refreshAccessToken({ refreshToken }: AuthRefreshAccessTokenRequest) {
|
||||
checkDedicatedProxy({
|
||||
customer
|
||||
}: AuthSignInResponse) {
|
||||
const DEDICATED_PROXY = process.env.DEDICATED_PROXY || '';
|
||||
this.logger.info('SignIn - Setting customer ID for dedicated proxy: ' + DEDICATED_PROXY);
|
||||
this.logger.info('Customer ID: ' + customer.id);
|
||||
|
||||
if (DEDICATED_PROXY !== '' && DEDICATED_PROXY !== customer.id) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
// Bloquear o customer de acesso o maestro publico
|
||||
this.logger.info('Check if customer have network policy: ' + customer.modules);
|
||||
const hasNetworkPolicyModule = customer.modules.includes('network-policy');
|
||||
if (hasNetworkPolicyModule && DEDICATED_PROXY === '') {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
||||
async signIn(
|
||||
{ username, password, totp }: AuthSignInRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('SignIn');
|
||||
|
||||
let result: AuthSignInResponse;
|
||||
|
||||
try {
|
||||
result = await lastValueFrom(
|
||||
this.authService.AuthSignIn({ username, password, totp }, metadata),
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('SignIn - Error during sign-in');
|
||||
this.logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (result.customer) {
|
||||
this.checkDedicatedProxy(result);
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async refreshAccessToken(
|
||||
{ refreshToken, userId }: AuthRefreshAccessTokenRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('RefreshAccessToken');
|
||||
|
||||
return lastValueFrom(
|
||||
this.authService.AuthRefreshAccessToken({ refreshToken }),
|
||||
this.authService.AuthRefreshAccessToken({ refreshToken, userId }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,10 +135,15 @@ export class AuthClientService implements OnModuleInit {
|
||||
);
|
||||
}
|
||||
|
||||
async resetPassword({ username }: AuthResetPasswordRequest) {
|
||||
async resetPassword(
|
||||
{ username }: AuthResetPasswordRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('resetPassword');
|
||||
|
||||
return lastValueFrom(this.authService.AuthResetPassword({ username }));
|
||||
return lastValueFrom(
|
||||
this.authService.AuthResetPassword({ username }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
async verifyResetPasswordCode({
|
||||
@@ -92,19 +157,21 @@ export class AuthClientService implements OnModuleInit {
|
||||
);
|
||||
}
|
||||
|
||||
async confirmResetPassword({
|
||||
username,
|
||||
code,
|
||||
newPassword,
|
||||
}: AuthConfirmResetPasswordRequest) {
|
||||
async confirmResetPassword(
|
||||
{ username, code, newPassword }: AuthConfirmResetPasswordRequest,
|
||||
metadata: Metadata,
|
||||
) {
|
||||
this.logger.info('confirmResetPassword');
|
||||
|
||||
return lastValueFrom(
|
||||
this.authService.AuthConfirmResetPassword({
|
||||
username,
|
||||
code,
|
||||
newPassword,
|
||||
}),
|
||||
this.authService.AuthConfirmResetPassword(
|
||||
{
|
||||
username,
|
||||
code,
|
||||
newPassword,
|
||||
},
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,4 +204,83 @@ export class AuthClientService implements OnModuleInit {
|
||||
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: '' }),
|
||||
);
|
||||
}
|
||||
|
||||
async blockUsers(
|
||||
users: string[],
|
||||
metadata: Metadata,
|
||||
): Promise<BulkEditResponse> {
|
||||
this.logger.info('blockUsers - Service starting');
|
||||
|
||||
try {
|
||||
this.logger.debug('Calling BlockUser gRPC method', {
|
||||
metadata: {
|
||||
access_token: metadata.get('access_token'),
|
||||
language: metadata.get('language'),
|
||||
},
|
||||
});
|
||||
|
||||
const response = await lastValueFrom<BulkEditResponse>(
|
||||
this.authService.BlockUser({ users }, metadata),
|
||||
);
|
||||
|
||||
this.logger.info('blockUsers - Service success', { response });
|
||||
return response;
|
||||
} catch (error) {
|
||||
this.logger.error('blockUsers - Service error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async unblockUsers(
|
||||
users: string[],
|
||||
metadata: Metadata,
|
||||
): Promise<BulkEditResponse> {
|
||||
this.logger.info('unblockUsers');
|
||||
return await lastValueFrom(
|
||||
this.authService.UnblockUser({ users }, metadata),
|
||||
);
|
||||
}
|
||||
|
||||
async resetUsers(
|
||||
users: string[],
|
||||
metadata: Metadata,
|
||||
): Promise<BulkEditResponse> {
|
||||
this.logger.info('resetUsers');
|
||||
|
||||
try {
|
||||
this.logger.debug('Calling ResetUser gRPC method', {
|
||||
metadata: {
|
||||
access_token: metadata.get('access_token'),
|
||||
language: metadata.get('language'),
|
||||
},
|
||||
});
|
||||
|
||||
const response = await lastValueFrom<BulkEditResponse>(
|
||||
this.authService.ResetUser({ users }, metadata),
|
||||
);
|
||||
|
||||
this.logger.info('resetUsers - Success', { response });
|
||||
return response;
|
||||
} catch (error) {
|
||||
this.logger.error('resetUsers - Error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,128 @@ 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()
|
||||
displayName: string;
|
||||
@ApiProperty()
|
||||
tier: string;
|
||||
|
||||
@ApiProperty()
|
||||
scheduleLimit: string;
|
||||
|
||||
@ApiProperty()
|
||||
links: Link[];
|
||||
|
||||
@ApiProperty()
|
||||
themeEnabled: boolean;
|
||||
@ApiProperty()
|
||||
enforceMfa: boolean;
|
||||
}
|
||||
|
||||
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()
|
||||
userId: string;
|
||||
}
|
||||
export class AuthRefreshAccessTokenRes {
|
||||
@ApiProperty()
|
||||
permissions: string[];
|
||||
@ApiProperty()
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface BulkEditRequest {
|
||||
users: string[];
|
||||
}
|
||||
|
||||
export interface BulkEditResponse {
|
||||
message: string;
|
||||
successfulUsers: string[];
|
||||
failedUsers: string[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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'],
|
||||
};
|
||||
console.log("GoogleLoginStrategy", options.clientID, options.callbackURL);
|
||||
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,42 @@
|
||||
import {
|
||||
ClientsProviderAsyncOptions,
|
||||
GrpcOptions,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { Catalog } from '@dadosfera/protospack-v2';
|
||||
import { PlatformInterfaces } 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,
|
||||
PlatformInterfaces.ProtoPackages.WritePackage
|
||||
],
|
||||
credentials: isLocalConnection ? undefined : credentials.createSsl(),
|
||||
protoPath: [
|
||||
Catalog.ProtoPaths.ReadFilePath,
|
||||
Catalog.ProtoPaths.WriteFilePath,
|
||||
PlatformInterfaces.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,31 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
|
||||
import { CatalogController } from './catalog.controller';
|
||||
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';
|
||||
import { CustomersModule } from '../customers/customers.module';
|
||||
import { ShareModule } from './share/share.module';
|
||||
import { CatalogService } from './catalog.service';
|
||||
import { MixpanelModule } from '../mixpanel/mixpanel.module';
|
||||
|
||||
const client = new CatalogClientConfiguration();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.register([client.providerOptions]),
|
||||
OldPipelineModule,
|
||||
UsersModule,
|
||||
RolesModule,
|
||||
CustomersModule,
|
||||
ShareModule,
|
||||
],
|
||||
controllers: [CatalogController],
|
||||
providers: [CatalogService, DadosferaLogger],
|
||||
exports: [CatalogService],
|
||||
})
|
||||
export class CatalogModule {}
|
||||
+1126
-199
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Constantes relacionadas ao Autodrive
|
||||
*
|
||||
*/
|
||||
export const AUTODRIVE_CONSTANTS = {
|
||||
// URLs e endpoints (apenas do ENV)
|
||||
BASE_URL: process.env.BASE_URL_AUTODRIVE || process.env.AUTODRIVE_BASE_URL,
|
||||
|
||||
// Credenciais (apenas do ENV, sem fallback para segurança)
|
||||
USERNAME: process.env.AUTODRIVE_USERNAME,
|
||||
PASSWORD: process.env.AUTODRIVE_PASSWORD,
|
||||
|
||||
// Modelo padrão
|
||||
DEFAULT_MODEL: process.env.AUTODRIVE_MODEL || "gpt-4o",
|
||||
|
||||
// Timeouts (em milissegundos)
|
||||
ASK_TIMEOUT: 120000,
|
||||
ANSWER_TIMEOUT: 180000,
|
||||
|
||||
// Headers
|
||||
HEADERS: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Chaves geográficas para detecção de dados de localização
|
||||
*/
|
||||
export const GEOGRAPHIC_KEYS = [
|
||||
'country', 'countries', 'city', 'cities',
|
||||
'region', 'regions', 'location', 'state',
|
||||
'states', 'address'
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Países comuns para detecção automática
|
||||
*/
|
||||
export const COMMON_COUNTRIES = [
|
||||
'brazil', 'brasil', 'usa', 'united states',
|
||||
'canada', 'mexico', 'argentina', 'chile',
|
||||
'colombia'
|
||||
] as const;
|
||||
@@ -0,0 +1,330 @@
|
||||
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;
|
||||
}
|
||||
|
||||
export type AssetReporter = {
|
||||
id: string;
|
||||
display_name: string;
|
||||
data_asset_type: string;
|
||||
created_at: string;
|
||||
tags: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class PiiDto {
|
||||
database_name: string;
|
||||
table_schema: string;
|
||||
table_name: string;
|
||||
column_name: string;
|
||||
data_type: string;
|
||||
pii_rules: string;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
|
||||
*/
|
||||
export const AI_DOCUMENTATION_PROMPT = `crie uma documentação em Portugues, Ingles e Espanhol seguindo essas instruções
|
||||
1. Persona: como profissional de governança e engenharia de dados
|
||||
2. Tarefa: ao receber as informações da tabela criar uma documentação com o seguinte escopo
|
||||
**A primeira linha do documento tem que conter a seguinte informação: ## Document languages: EN / BR / ES
|
||||
**A segunda linha tem que obrigatoriamente conter a escrita Table: nome da tabela
|
||||
**A terceira linha tem que obrigatoriamente conter a escrita Table Schema: nome do table schema
|
||||
**DIRETRIZ CRUCIAL DE CONSISTÊNCIA E COMPLETUDE DE SCHEMA:**
|
||||
**1. Fonte Exclusiva de Metadados:** O 'Table Schema' definido na linha acima é a ÚNICA fonte de verdade para o schema dos dados a serem documentados. TODAS as informações subsequentes, especialmente na seção 'Estrutura da Tabela' (incluindo a lista de colunas, seus nomes, tipos de dados, descrições e exemplos) DEVEM ser extraídas EXCLUSIVAMENTE de metadados que correspondem a ESTE 'Table Schema'. Se os dados de entrada que você recebeu contiverem informações para a mesma tabela ou colunas mas de schemas diferentes (ex: um schema 'bronze' e um 'silver'), você DEVE IGNORAR TOTALMENTE as informações dos schemas divergentes para esta tarefa de documentação e utilizar APENAS as do 'Table Schema' aqui especificado.
|
||||
**2. Listagem Completa de Colunas:** Sua principal tarefa na seção 'Estrutura da Tabela' é identificar e listar TODAS as colunas que pertencem ao 'Table Schema' especificado. Verifique nos dados de entrada fornecidos se há uma indicação explícita do número total de colunas para esta tabela neste schema (por exemplo, um campo como 'Num_columns' ou similar nos metadados da tabela). Você deve se esforçar para listar exatamente essa quantidade de colunas. Se essa contagem não estiver disponível, liste todas as colunas que você puder identificar como pertencentes exclusivamente a este 'Table Schema'. A completude em relação ao schema especificado é essencial.
|
||||
|
||||
**Depois de "Estrutura da tablea", incluir a mensagem "Este documento foi gerado por IA", traduzida corretamente para cada idioma.**
|
||||
**Obrigatoriamente:Após finalizar a versão em Inglês, começar a versão em Português** **Após finalizar a versão em Português, começar a versão em Espanhol** **Antes de começar cada versão, colocar um título como:** - \`## English Version\` (para inglês)
|
||||
- \`## Versão em Português\` (para português)
|
||||
- \`## Versión en Español\` (para espanhol)
|
||||
- Descrição: fornece uma visão geral do ativo de dados,
|
||||
destacando seu propósito e principal funcionalidade.
|
||||
Esta sessão resume o conteúdo e o objetivo do ativo, ajudando os usuários a entender rapidamente o que o ativo representa
|
||||
e como pode ser utilizado em suas análises e decisões.
|
||||
- Sugestão de Domínio de Dados:
|
||||
Analise cuidadosamente os dados da tabela e sugira o domínio mais apropriado. Inclua:
|
||||
- Domínio Sugerido: [Nome do domínio]
|
||||
- Motivo: [Explicação breve sobre porque a tabela pertence a este domínio]
|
||||
- Observações: [Qualquer observação adicional relevante]
|
||||
|
||||
Exemplos de Domínios de Dados para referência:
|
||||
- Financeiro: Dados sobre transações, receitas, despesas, etc.
|
||||
- Recursos Humanos: Dados sobre funcionários, cargos, salários, etc.
|
||||
- Produtos: Dados sobre produtos, categorias, preços, etc.
|
||||
- Fornecedores: Dados sobre fornecedores, produtos fornecidos, localizações, etc.
|
||||
- Marketing: Dados sobre campanhas, leads, conversões, etc.
|
||||
- Vendas: Dados sobre vendas, clientes, produtos vendidos, etc.
|
||||
- Operações: Dados sobre processos, logística, produção, etc.
|
||||
- Clientes: Dados sobre clientes, interações, histórico, etc.
|
||||
-Tags Sugeridas:
|
||||
A IA deve gerar tags relevantes **com base nos dados da tabela**.
|
||||
- **IMPORTANTE: Analise cuidadosamente os dados de preview da tabela (PREVIEW DATA) para encontrar países. Procure em todas as colunas por nomes de países, cidades ou regiões.**
|
||||
- **Garanta que as tags estejam separadas por espaços vazios, todas na mesma linha, exemplo: #marketing #sales #australia #canada, limitar até 3 países que mais aparecem** - **Os países DEVEM ser extraídos dos dados de preview da tabela. Procure em colunas como City, Country, Region, Location, etc.** - Por que esta tabela é interessante:
|
||||
Nesta sessão, é destacada a importância do ativo, explicando como ele pode ser útil para os usuários.
|
||||
São abordadas as formas como o ativo pode melhorar a tomada de decisões, identificar padrões relevantes ou fornecer insights valiosos.
|
||||
O objetivo é ressaltar a utilidade prática e o impacto positivo que o ativo pode ter em suas atividades.
|
||||
- Análises potencialmente úteis feitas com esses dados:
|
||||
Aqui são listadas algumas das análises que podem ser realizadas com o ativo de dados. Inclui sugestões de dashboards,
|
||||
relatórios ou outros tipos de análises que aproveitam as informações fornecidas pelo ativo.
|
||||
O objetivo é oferecer maneiras de utilizar os dados para obter insights valiosos e apoiar a tomada de decisões informadas.
|
||||
- Links Úteis:
|
||||
Os Links Úteis oferecem recursos adicionais relacionados ao ativo de dados, incluindo guias,
|
||||
artigos ou outras fontes de informação que podem ajudar os usuários a compreender melhor o ativo e suas aplicações. Além disso,
|
||||
inclui um link rápido dentro da Dadosfera para ativos relacionados diretamente com o ativo em questão, facilitando a navegação entre os ativos.
|
||||
- Estrutura da Tabela:
|
||||
A Estrutura da Tabela detalha TODAS as colunas e os dados disponíveis no ativo, conforme pertencentes ao 'Table Schema' principal definido no início deste documento.
|
||||
**Instrução Detalhada para Estrutura da Tabela:**
|
||||
Siga rigorosamente estes passos:
|
||||
1. Identifique nos dados de entrada (metadados da tabela e das colunas) todas as colunas que pertencem EXCLUSIVAMENTE ao 'Table Schema' especificado no cabeçalho deste documento. Se houver uma contagem de colunas (ex: 'Num_columns') para este schema específico, assegure-se de listar essa quantidade.
|
||||
2. Para CADA uma dessas colunas identificadas, formate a saída da seguinte maneira, **SEM utilizar NENHUM marcador de lista (como traços ou asteriscos) no início de cada entrada de coluna**. Cada coluna deve ser apresentada como um bloco de texto. Inclua uma linha em branco entre a documentação de cada coluna para separação visual.
|
||||
- Apresente o NOME_DA_COLUNA em maiúsculas, seguido pelo (TIPO_DE_DADO_EXTRAÍDO_DOS_METADADOS_DO_SCHEMA_CORRETO) entre parênteses.
|
||||
- O **NOME_DA_COLUNA (TIPO_DE_DADO_EXTRAÍDO_DOS_METADADOS_DO_SCHEMA_CORRETO)** deve estar na primeira linha do bloco da coluna e **inteiramente em negrito**.
|
||||
- Na linha seguinte, a etiqueta "**Descrição:**" deve estar **em negrito**, seguida pelo texto da descrição da coluna.
|
||||
- Na linha seguinte à descrição, a etiqueta "**Exemplo:**" deve estar **em negrito**, seguida pelo valor do exemplo. Se o exemplo for um valor literal ou código, formate-o entre crases (\`) se apropriado.
|
||||
- Se houver informações adicionais relevantes (como "Valores Possíveis:", "Observações:", etc.), coloque a etiqueta correspondente **em negrito** em uma nova linha, seguida pelo seu texto.
|
||||
|
||||
Este documento foi gerado por IA.
|
||||
|
||||
NOME_COLUNA_1 (TIPO_DADO_SCHEMA_CORRETO_1):
|
||||
Descrição: [Descrição da coluna 1, do schema correto]
|
||||
Exemplo: \`[Exemplo de valor para coluna 1, do schema correto]\`
|
||||
|
||||
NOME_COLUNA_2 (TIPO_DADO_SCHEMA_CORRETO_2):
|
||||
Descrição: [Descrição da coluna 2, do schema correto]
|
||||
Exemplo: \`[Exemplo de valor para coluna 2, do schema correto]\`
|
||||
|
||||
(continue este formato com início de cada coluna para TODAS as colunas do 'Table Schema' especificado, garanta com que NUNCA tenha TRAÇO OU PONTO no inicio)
|
||||
|
||||
3. Contexto : O usuário ira cadastrar um ativo de dados na nossa plataforma e para ter um bom catalogo ele ira querer gerar a documentação padronizada mas explicativa e
|
||||
automática
|
||||
4. Restrições : A documentação deve seguir obrigatoriamente o mesmo padrão principalmente na parte de estrutura de dados
|
||||
5. Objetivo: O principal objetivo é gerar uma documentação acessível, clara,
|
||||
automática e padronizada para os usuários que desejem cadastrar um ativo de dados na plataforma`;
|
||||
|
||||
/**
|
||||
* Configurações para a geração de documentação com IA
|
||||
*/
|
||||
export const AI_DOCUMENTATION_CONFIG = {
|
||||
FETCH_K: 250,
|
||||
K: 100,
|
||||
} as const;
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Inject,
|
||||
Param,
|
||||
Req,
|
||||
UseFilters,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiHeaders,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||
import {
|
||||
IColumnsMetadataResponse,
|
||||
IDocsResponse,
|
||||
IPreviewResponse,
|
||||
|
||||
} from '../dtos';
|
||||
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 { ShareService } from './share.service';
|
||||
import { Request } from 'express';
|
||||
|
||||
@ApiTags('Catalog')
|
||||
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||
@Controller('catalog/data-asset/share')
|
||||
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||
export class ShareController {
|
||||
logger: DadosferaLogger;
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
private catalogShareService: ShareService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
async getShareDataAsset(
|
||||
@Param('id') id: string,
|
||||
@Req() request: Request
|
||||
) {
|
||||
this.logger.info(`GET //:id`);
|
||||
return await this.catalogShareService.getOneDataAssetPublic(id, request);
|
||||
}
|
||||
|
||||
@Get('/:id/columns-metadata')
|
||||
async getShareDataAssetColumnsMetadata(
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('id') id: string,
|
||||
@Req() request: Request
|
||||
): Promise<IColumnsMetadataResponse> {
|
||||
this.logger.info(`GET /:id/columns-metadata`);
|
||||
|
||||
const columns_metadata =
|
||||
await this.catalogShareService.getDatasetColumnsMetadata(id, request);
|
||||
|
||||
|
||||
return { columns_metadata };
|
||||
}
|
||||
|
||||
@Get('/:id/preview')
|
||||
async getShareDataAssetPreview(
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('id') id: string,
|
||||
@Req() request: Request
|
||||
): Promise<IPreviewResponse> {
|
||||
this.logger.info(`GET /:id/preview`);
|
||||
const preview = await this.catalogShareService.getDatasetPreview(id, request);
|
||||
|
||||
return { preview };
|
||||
}
|
||||
|
||||
@Get('/:id/docs')
|
||||
async getShareDataAssetDocs(
|
||||
@Language() language: LanguageEnum,
|
||||
@Param('id') id: string,
|
||||
@Req() request: Request
|
||||
): Promise<IDocsResponse> {
|
||||
this.logger.info(`GET /:id/docs`);
|
||||
const docs = await this.catalogShareService.getDataDocs(id, request);
|
||||
|
||||
return { docs };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { CatalogClientConfiguration } from "../catalog-client";
|
||||
import { ClientsModule } from "@nestjs/microservices";
|
||||
import { RolesModule } from "src/modules/roles/roles.module";
|
||||
import { UsersModule } from "src/modules/users/users.module";
|
||||
import { CustomersModule } from "src/modules/customers/customers.module";
|
||||
import { ShareMetadataModule } from "src/modules/share-metadata/share-metadata.module";
|
||||
import { ShareController } from "./share.controller";
|
||||
import DadosferaLogger from "@dadosfera/dadosfera-logs";
|
||||
import { ShareService } from "./share.service";
|
||||
import { MixpanelModule } from "src/modules/mixpanel/mixpanel.module";
|
||||
import { AuthModule } from "src/modules/auth/auth.module";
|
||||
|
||||
const client = new CatalogClientConfiguration();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.register([client.providerOptions]),
|
||||
UsersModule,
|
||||
RolesModule,
|
||||
CustomersModule,
|
||||
ShareMetadataModule,
|
||||
MixpanelModule,
|
||||
AuthModule
|
||||
],
|
||||
controllers: [ShareController],
|
||||
providers: [ShareService, DadosferaLogger],
|
||||
exports: [ShareModule],
|
||||
})
|
||||
export class ShareModule {}
|
||||
@@ -0,0 +1,275 @@
|
||||
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||
import {
|
||||
ProtoServices,
|
||||
ReadService,
|
||||
} from '@dadosfera/protospack-v2/dist/lib/Catalog';
|
||||
import {
|
||||
ForbiddenException,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { CatalogClientConfiguration } from '../catalog-client';
|
||||
import { ClientGrpc } from '@nestjs/microservices';
|
||||
import { UsersService } from 'src/modules/users/users.service';
|
||||
import { RolesService } from 'src/modules/roles/roles.service';
|
||||
import { RequestUser } from 'src/decorators/user.decorator';
|
||||
import { PackTheMetadata } from 'src/utils/PackTheMetadata';
|
||||
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||
import { Metadata } from '@grpc/grpc-js';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ShareMetadataService } from 'src/modules/share-metadata/share-metadata.service';
|
||||
import { isJWT } from 'class-validator';
|
||||
import { MixpanelService } from 'src/modules/mixpanel/mixpanel.service';
|
||||
import { Request } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AuthClientService } from 'src/modules/auth/auth.service';
|
||||
|
||||
|
||||
export class ShareService implements OnModuleInit {
|
||||
catalogReadService: ReadService.CatalogReadServices;
|
||||
logger: DadosferaLogger;
|
||||
|
||||
constructor(
|
||||
@Inject(DadosferaLogger)
|
||||
dadosferaLogger: DadosferaLogger,
|
||||
@Inject(CatalogClientConfiguration.name)
|
||||
private readonly grpcClient: ClientGrpc,
|
||||
private readonly userService: UsersService,
|
||||
private readonly roleService: RolesService,
|
||||
private readonly shareMetadataService: ShareMetadataService,
|
||||
private readonly mixpanelService: MixpanelService,
|
||||
private authClient: AuthClientService,
|
||||
) {
|
||||
this.logger = dadosferaLogger.logger;
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.catalogReadService =
|
||||
this.grpcClient.getService<ReadService.CatalogReadServices>(
|
||||
ProtoServices.CatalogReadServices,
|
||||
);
|
||||
}
|
||||
|
||||
async getDatasetColumnsMetadata(id: string, request: Request) {
|
||||
const shareMetadata = await this.getShareMetadata(id, request);
|
||||
const metadata = PackTheMetadata({
|
||||
customer_id: shareMetadata.customerId,
|
||||
customer_name: shareMetadata.customerName,
|
||||
});
|
||||
const { columns_metadata } = await lastValueFrom(
|
||||
this.catalogReadService.GetDatasetColumnsMetadata(
|
||||
{ id: shareMetadata.assetId, type: undefined },
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
const result = JSON.parse(columns_metadata);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getDatasetPreview(id: string, request: Request) {
|
||||
const shareMetadata = await this.getShareMetadata(id, request);
|
||||
const metadata = PackTheMetadata({
|
||||
customer_id: shareMetadata.customerId,
|
||||
customer_name: shareMetadata.customerName,
|
||||
});
|
||||
const { preview } = await lastValueFrom(
|
||||
this.catalogReadService.GetDatasetPreview(
|
||||
{ id: shareMetadata.assetId, type: undefined },
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
const result = JSON.parse(preview);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getOneDataAssetPublic(id: string, request: Request) {
|
||||
this.logger.info("getOneDataAssetPublic: " + JSON.stringify({
|
||||
id
|
||||
}))
|
||||
try {
|
||||
const user = await this.getUserFromRequest(request);
|
||||
|
||||
const shareMetadata = await this.getShareMetadata(id, request);
|
||||
|
||||
const mixpanelTracker = {
|
||||
asset: shareMetadata.assetId,
|
||||
type: isJWT(id) ? 'assigned' : shareMetadata.type,
|
||||
customer: shareMetadata.customerName
|
||||
}
|
||||
|
||||
if (user) {
|
||||
await this.mixpanelService.track("share_page", user, request, mixpanelTracker);
|
||||
} else {
|
||||
await this.mixpanelService.trackShare(request, mixpanelTracker);
|
||||
}
|
||||
|
||||
this.logger.info("shareMetadata: " + JSON.stringify(shareMetadata))
|
||||
const metadata = PackTheMetadata({
|
||||
customer_id: shareMetadata.customerId,
|
||||
customer_name: shareMetadata.customerName,
|
||||
});
|
||||
|
||||
const { data_asset } = await this.getOneDataAsset({
|
||||
customer_id: shareMetadata.customerId,
|
||||
id: shareMetadata.assetId,
|
||||
metadata,
|
||||
});
|
||||
this.logger.info('found asset: ' + JSON.stringify(data_asset));
|
||||
delete data_asset.p_roles;
|
||||
delete data_asset.p_users;
|
||||
data_asset.share_type = 'public';
|
||||
if (data_asset.share_type !== 'public') throw new NotFoundException();
|
||||
|
||||
return { data_asset };
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async getOneDataAsset(data: {
|
||||
id: string;
|
||||
customer_id: string;
|
||||
metadata: Metadata;
|
||||
}) {
|
||||
const { customer_id, id, metadata } = data;
|
||||
const { data_asset } = await lastValueFrom(
|
||||
this.catalogReadService.GetOneDataAsset(
|
||||
{ id, type: undefined },
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
let asset = JSON.parse(data_asset);
|
||||
asset = {
|
||||
...asset,
|
||||
p_roles: asset.roles,
|
||||
p_users: asset.users,
|
||||
};
|
||||
asset = await this.getAssetsUsersAndRoles([asset], customer_id);
|
||||
|
||||
return { data_asset: asset[0] };
|
||||
}
|
||||
|
||||
async getDataDocs(id: string, request: Request) {
|
||||
const shareMetadata = await this.getShareMetadata(id, request);
|
||||
const metadata = PackTheMetadata({
|
||||
customer_id: shareMetadata.customerId,
|
||||
customer_name: shareMetadata.customerName,
|
||||
});
|
||||
|
||||
const { documentation } = await lastValueFrom(
|
||||
this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata),
|
||||
);
|
||||
console.log(documentation);
|
||||
const docs = JSON.parse(documentation);
|
||||
return docs;
|
||||
}
|
||||
|
||||
private 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_assets.map((data_asset) => {
|
||||
const owner = customer_users.find(
|
||||
(u) => u.id === data_asset.owner,
|
||||
)?.username;
|
||||
|
||||
const roles = [];
|
||||
const users = [];
|
||||
for (const role_id of data_asset.roles) {
|
||||
const role = customer_roles.find((r) => r.id === role_id);
|
||||
if (role) roles.push({ id: role.id, name: role.name });
|
||||
}
|
||||
for (const user_id of data_asset.users) {
|
||||
const user = customer_users.find((r) => r.id === user_id);
|
||||
if (user) users.push({ id: user.id, username: user.username });
|
||||
}
|
||||
return {
|
||||
...data_asset,
|
||||
roles,
|
||||
users,
|
||||
owner,
|
||||
} as typeof data_asset;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async getShareMetadata(id: string, request: Request) {
|
||||
const metadata = PackTheMetadata({});
|
||||
this.logger.info('GET share metadata')
|
||||
const info = await this.shareMetadataService.get(id, metadata);
|
||||
if (isJWT(id) && info ){
|
||||
return info;
|
||||
}
|
||||
|
||||
const user = await this.getUserFromRequest(request);
|
||||
|
||||
if (info.type === 'private') {
|
||||
if (!user) {
|
||||
throw new ForbiddenException(
|
||||
'You do not have permission to access this data asset.',
|
||||
);
|
||||
}
|
||||
|
||||
const is_data_manager = user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER.seqid,
|
||||
);
|
||||
|
||||
const is_get = user.permissions.includes(
|
||||
PERMISSIONS_GROUPS.CATALOG.permissions.GET.seqid,
|
||||
);
|
||||
|
||||
if (is_data_manager || is_get) {
|
||||
return info;
|
||||
}
|
||||
|
||||
throw new ForbiddenException(
|
||||
'You do not have permission to access this data asset.',
|
||||
);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private async getUserFromRequest(request: Request): Promise<RequestUser | null> {
|
||||
const accessToken = request.get('Authorization');
|
||||
if (accessToken) {
|
||||
const accessTokenDecoded: any = jwt.decode(accessToken, {
|
||||
complete: true,
|
||||
});
|
||||
|
||||
const { kid } = accessTokenDecoded.header;
|
||||
|
||||
const { keys } = await this.authClient.getPublicKeys();
|
||||
|
||||
const pemValue = keys.find((key) => key.kid === kid);
|
||||
|
||||
if (!pemValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
jwt.verify(accessToken, pemValue.pem);
|
||||
const accessTokenPayload = accessTokenDecoded.payload;
|
||||
|
||||
return {
|
||||
user_id: accessTokenPayload.user_id,
|
||||
username: accessTokenPayload.username,
|
||||
permissions: accessTokenPayload.permissions,
|
||||
customer_id: accessTokenPayload.customer_id,
|
||||
customer_name: accessTokenPayload.customer_name,
|
||||
customer_tier: accessTokenPayload.customer_tier,
|
||||
customer_modules: accessTokenPayload.customer_modules,
|
||||
access_token: accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Tipos relacionados à geração de documentação com IA
|
||||
*/
|
||||
|
||||
export interface AutodriveCredentials {
|
||||
username: string;
|
||||
password: string;
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
authHeader?: string;
|
||||
}
|
||||
|
||||
export interface AutodriveAskPayload {
|
||||
question: string;
|
||||
fetch_k: number;
|
||||
k: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
export interface AutodriveAskResponse {
|
||||
answer?: string;
|
||||
question_id?: string;
|
||||
dataset_id?: string;
|
||||
}
|
||||
|
||||
export interface AutodriveAnswerResponse {
|
||||
status: 'started' | 'success' | 'failed';
|
||||
answer?: string;
|
||||
status_reason?: string;
|
||||
}
|
||||
|
||||
export interface AutodriveUploadResponse {
|
||||
dataset_id: string;
|
||||
}
|
||||
|
||||
export interface DatasetStatusResponse {
|
||||
status: 'processing' | 'success' | 'failed';
|
||||
status_reason?: string;
|
||||
}
|
||||
|
||||
export interface ColumnData {
|
||||
name: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
nullable?: string;
|
||||
}
|
||||
|
||||
export interface ColumnsMetadata {
|
||||
columns: ColumnData[];
|
||||
}
|
||||
|
||||
export interface DataPreview {
|
||||
preview: any[];
|
||||
}
|
||||
|
||||
export interface FormattedDataForAI {
|
||||
dataAsset: any;
|
||||
dataPreview: any[];
|
||||
columnsData?: ColumnsMetadata;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,31 +1,40 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import { ClientOptions, Transport } from '@nestjs/microservices';
|
||||
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 {
|
||||
config(): ClientOptions {
|
||||
return {
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
url: process.env.INFACTORY_URL,
|
||||
package: [
|
||||
ConnectorManager.ProtoPackages.WritePackage,
|
||||
ConnectorManager.ProtoPackages.ReadPackage,
|
||||
],
|
||||
// credentials: undefined,
|
||||
credentials: process.env.LOCAL_ENV
|
||||
? undefined
|
||||
: credentials.createSsl(),
|
||||
protoPath: [
|
||||
ConnectorManager.ProtoPaths.WriteFilePath,
|
||||
ConnectorManager.ProtoPaths.ReadFilePath,
|
||||
],
|
||||
loader: {
|
||||
enums: String,
|
||||
objects: true,
|
||||
arrays: true,
|
||||
},
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user