Compare commits
2605 Commits
debug/memo
...
AUT-1018
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0526ec5b06 | ||
![]() |
061a9c6947 | ||
![]() |
ab307cdee0 | ||
![]() |
9d5dac1701 | ||
![]() |
751eb41e72 | ||
![]() |
f08dc25711 | ||
![]() |
737eb31776 | ||
![]() |
d6abf283bc | ||
![]() |
bac4ab5aa4 | ||
![]() |
b5839390fd | ||
![]() |
d19271dae1 | ||
![]() |
ef5a09314e | ||
![]() |
ba52e298eb | ||
![]() |
b3c3998189 | ||
![]() |
782f9b5c04 | ||
![]() |
3079d8c605 | ||
![]() |
c5202d7b3e | ||
![]() |
fbae83f4de | ||
![]() |
5b7b8c934f | ||
![]() |
b70223e824 | ||
![]() |
9900bbbc8d | ||
![]() |
fc7f1ddd69 | ||
![]() |
991b2f4bf7 | ||
![]() |
bb251b16a9 | ||
![]() |
1b34a48a61 | ||
![]() |
8c83b715fe | ||
![]() |
c122708b0b | ||
![]() |
258d920ff2 | ||
![]() |
6e5c0cc0c7 | ||
![]() |
9dc82290b5 | ||
![]() |
bb73f90374 | ||
![]() |
8a0720b0e3 | ||
![]() |
88468c4f89 | ||
![]() |
d19a45592f | ||
![]() |
21a921d25d | ||
![]() |
3b2946aac5 | ||
![]() |
196d555e8c | ||
![]() |
28f39b5c7e | ||
![]() |
ec8ac17f4a | ||
![]() |
c45573349a | ||
![]() |
d36c9d43f6 | ||
![]() |
b06c744392 | ||
![]() |
9548c93b4c | ||
![]() |
4144944ab2 | ||
![]() |
46b85519c1 | ||
![]() |
5a83fc33ec | ||
![]() |
c80791267f | ||
![]() |
b30f97db3e | ||
![]() |
717c81fa2b | ||
![]() |
ae188bc563 | ||
![]() |
fc4561221d | ||
![]() |
5aeb4f8809 | ||
![]() |
c6c900bc39 | ||
![]() |
c18ab67a25 | ||
![]() |
55ae1470d0 | ||
![]() |
a1136fdfb2 | ||
![]() |
dfe56d3aa2 | ||
![]() |
3da5e13ecd | ||
![]() |
40d0fe0db6 | ||
![]() |
029fd2d0b0 | ||
![]() |
12905ad733 | ||
![]() |
d257f59a3e | ||
![]() |
1dc9646894 | ||
![]() |
c9281b4605 | ||
![]() |
e9d2ae5d67 | ||
![]() |
0f8e05610b | ||
![]() |
9ea2196e51 | ||
![]() |
97327a9033 | ||
![]() |
8bf11ba7d1 | ||
![]() |
523833b015 | ||
![]() |
e25a651d26 | ||
![]() |
92a8c1483d | ||
![]() |
8da3448e9c | ||
![]() |
f2385d8916 | ||
![]() |
17a8daa526 | ||
![]() |
51e254f127 | ||
![]() |
feccf571cd | ||
![]() |
9f8ce44c1b | ||
![]() |
a7cfe7f23b | ||
![]() |
8eaf775ef2 | ||
![]() |
7b4179a87f | ||
![]() |
43281bcbfe | ||
![]() |
8a35d47caf | ||
![]() |
759e8b6c42 | ||
![]() |
a8b01244af | ||
![]() |
6ffb16ac67 | ||
![]() |
5b66cc6c8b | ||
![]() |
9a96258265 | ||
![]() |
a6cc1566c7 | ||
![]() |
d8d6227125 | ||
![]() |
fbfa67e471 | ||
![]() |
ab897ada5a | ||
![]() |
3bcd3f3cb7 | ||
![]() |
acbede8631 | ||
![]() |
fa8c7571d7 | ||
![]() |
a58575c5a1 | ||
![]() |
51f7009a80 | ||
![]() |
ba24c77f06 | ||
![]() |
6b712c9a90 | ||
![]() |
033b15a158 | ||
![]() |
e398bb84d4 | ||
![]() |
05e902ab0c | ||
![]() |
36eee61cb5 | ||
![]() |
2409ce6fce | ||
![]() |
999d61e520 | ||
![]() |
08d2418190 | ||
![]() |
6c07faeaaf | ||
![]() |
3f5cfbf5a2 | ||
![]() |
28f7707c75 | ||
![]() |
dc8358f6ce | ||
![]() |
ffcda04677 | ||
![]() |
64ef655abb | ||
![]() |
67887b1220 | ||
![]() |
07803d1263 | ||
![]() |
f5ff7f7e13 | ||
![]() |
641d062b82 | ||
![]() |
bb28a06ee9 | ||
![]() |
81338c60f2 | ||
![]() |
f47fa5d272 | ||
![]() |
29e9e012a5 | ||
![]() |
f89ddb5847 | ||
![]() |
c721f063ef | ||
![]() |
a709565336 | ||
![]() |
91e484aef1 | ||
![]() |
6ba94dcc8e | ||
![]() |
788530be45 | ||
![]() |
7ed392e854 | ||
![]() |
3932e554da | ||
![]() |
1a21624618 | ||
![]() |
9f292ff018 | ||
![]() |
dbb24b3a9b | ||
![]() |
35b2639837 | ||
![]() |
35951199cd | ||
![]() |
79af909c51 | ||
![]() |
3482aa7b76 | ||
![]() |
5dbc1f59ef | ||
![]() |
2166a3220e | ||
![]() |
24a7d1ef10 | ||
![]() |
18ffbb7317 | ||
![]() |
363874de6a | ||
![]() |
68d1719b11 | ||
![]() |
1a75d81268 | ||
![]() |
2163be4227 | ||
![]() |
b54afcd922 | ||
![]() |
0a86641a0f | ||
![]() |
18464c746a | ||
![]() |
ba92cddae1 | ||
![]() |
2a4f8ed45f | ||
![]() |
135a0028be | ||
![]() |
4da6e8372f | ||
![]() |
6a7cdf2570 | ||
![]() |
73c929f25e | ||
![]() |
754c2d41c2 | ||
![]() |
7201e48111 | ||
![]() |
e4292815cd | ||
![]() |
ab37250d5d | ||
![]() |
e5be8d3ba7 | ||
![]() |
96a421fa22 | ||
![]() |
12f72401b1 | ||
![]() |
7391a9eddc | ||
![]() |
30dee27f72 | ||
![]() |
51a9939034 | ||
![]() |
e03c6e0ca4 | ||
![]() |
bece5c6488 | ||
![]() |
d49bb4c52d | ||
![]() |
73d0eec30c | ||
![]() |
5c756b16ca | ||
![]() |
f482c2422c | ||
![]() |
2e564c863f | ||
![]() |
d9917a81bb | ||
![]() |
61dc431f92 | ||
![]() |
7d2fb8d9d7 | ||
![]() |
608b79b66f | ||
![]() |
009754c18b | ||
![]() |
5df07c289e | ||
![]() |
a36d10870b | ||
![]() |
b549ba3e39 | ||
![]() |
897c96361f | ||
![]() |
e7693d8aa6 | ||
![]() |
1fe755f836 | ||
![]() |
ea1a63f7dd | ||
![]() |
85134722a5 | ||
![]() |
5c9d3ed134 | ||
![]() |
17fb935ea0 | ||
![]() |
196642a1cf | ||
![]() |
009cf63d8c | ||
![]() |
da399aacd6 | ||
![]() |
3632ee77e5 | ||
![]() |
2901f337cc | ||
![]() |
f0bd2f335b | ||
![]() |
acdd026448 | ||
![]() |
fb0a328ab0 | ||
![]() |
d2a7889fc9 | ||
![]() |
88c50e014d | ||
![]() |
f0ef12f904 | ||
![]() |
1827f5413f | ||
![]() |
0609f30e25 | ||
![]() |
d4e4d95b6d | ||
![]() |
d74af4931e | ||
![]() |
bee043d10d | ||
![]() |
a65e48b98a | ||
![]() |
ee26b54d54 | ||
![]() |
855ec53dc2 | ||
![]() |
3e3e48110d | ||
![]() |
fc04a357c8 | ||
![]() |
c8147370de | ||
![]() |
999426be89 | ||
![]() |
91458f91ef | ||
![]() |
4b9ed29cc0 | ||
![]() |
e3bcb673fb | ||
![]() |
bf4776ca4f | ||
![]() |
9f7f30a92a | ||
![]() |
5c29fff55e | ||
![]() |
a0160c2573 | ||
![]() |
87d3ca287d | ||
![]() |
526e093689 | ||
![]() |
0930c9d8d6 | ||
![]() |
ec680a713d | ||
![]() |
583f90d1e9 | ||
![]() |
8ba95381bc | ||
![]() |
ec6d634b99 | ||
![]() |
bc082acbe7 | ||
![]() |
e474ba02cb | ||
![]() |
ea922aaf10 | ||
![]() |
766e6e20d8 | ||
![]() |
8e646c244e | ||
![]() |
26f31a5899 | ||
![]() |
5c79e374dd | ||
![]() |
7c1473ea95 | ||
![]() |
1fe4cc3258 | ||
![]() |
042ad4cea1 | ||
![]() |
e4c998dbce | ||
![]() |
83c8cacdac | ||
![]() |
f75d5d906e | ||
![]() |
85b3856564 | ||
![]() |
75cb2569b5 | ||
![]() |
0a4ac1cece | ||
![]() |
a873fd14bd | ||
![]() |
85b4cd4998 | ||
![]() |
e9bc9b1aa8 | ||
![]() |
e3bf599bf6 | ||
![]() |
01ae96840e | ||
![]() |
186160ebf4 | ||
![]() |
70f5e45c1f | ||
![]() |
6dc54ecabc | ||
![]() |
d21888c047 | ||
![]() |
33f7a90042 | ||
![]() |
a00d3a2c5e | ||
![]() |
abc64d769c | ||
![]() |
88754ac569 | ||
![]() |
e3ee05d47d | ||
![]() |
3b004e7483 | ||
![]() |
9aa48c20e4 | ||
![]() |
1b5d3beeca | ||
![]() |
00115d313e | ||
![]() |
190f1a205f | ||
![]() |
8ab6f0c3fe | ||
![]() |
13eea263c0 | ||
![]() |
b52b40962e | ||
![]() |
7d1fa2e40c | ||
![]() |
93b2098829 | ||
![]() |
a2acdc6b12 | ||
![]() |
38b2c1e30f | ||
![]() |
e07f579f3c | ||
![]() |
df3297b6ca | ||
![]() |
fc4eeed764 | ||
![]() |
3596d13be1 | ||
![]() |
104d49ea1c | ||
![]() |
7057317446 | ||
![]() |
280575df88 | ||
![]() |
d2cb434b7b | ||
![]() |
2ecb802a2e | ||
![]() |
46e706c415 | ||
![]() |
3a57349d8a | ||
![]() |
565db852e0 | ||
![]() |
754c3269ec | ||
![]() |
a079842408 | ||
![]() |
7664b58553 | ||
![]() |
de77488f7e | ||
![]() |
d808afd21b | ||
![]() |
b68aff76a1 | ||
![]() |
6da7fe158f | ||
![]() |
4dbc7fdc7d | ||
![]() |
ad1e1f7eca | ||
![]() |
9c3f7a3823 | ||
![]() |
86f4cb7701 | ||
![]() |
359a90245d | ||
![]() |
d8d7d86359 | ||
![]() |
7189b629c0 | ||
![]() |
55c9b5566c | ||
![]() |
ab671ccbf7 | ||
![]() |
316bda8c3f | ||
![]() |
76f77e8a4c | ||
![]() |
4a99d5eab7 | ||
![]() |
473d287c6d | ||
![]() |
bddd9896e4 | ||
![]() |
95eb115965 | ||
![]() |
9a63b213b0 | ||
![]() |
90b00d88f1 | ||
![]() |
ec87c7f21c | ||
![]() |
452f45cac6 | ||
![]() |
c644b3d384 | ||
![]() |
68160c20e8 | ||
![]() |
ad144206dd | ||
![]() |
f3d20ab769 | ||
![]() |
9767ca7116 | ||
![]() |
73a5b8553f | ||
![]() |
5c684cd499 | ||
![]() |
479f3e3172 | ||
![]() |
6a1350fd00 | ||
![]() |
563784da1c | ||
![]() |
347f0ed3a5 | ||
![]() |
1db9f5b2c2 | ||
![]() |
f2a3e26188 | ||
![]() |
1c0897bfb6 | ||
![]() |
f0793992a6 | ||
![]() |
393205ba2f | ||
![]() |
ab49535b6c | ||
![]() |
8191b48548 | ||
![]() |
925dd06432 | ||
![]() |
0d525e056a | ||
![]() |
3aa86eebf2 | ||
![]() |
d7a93abec0 | ||
![]() |
6448d28a18 | ||
![]() |
449976483c | ||
![]() |
e468b762ef | ||
![]() |
b578e73cc4 | ||
![]() |
e381f95b95 | ||
![]() |
5685afae63 | ||
![]() |
53a473422b | ||
![]() |
3f9f17f584 | ||
![]() |
2c410bf318 | ||
![]() |
40934a2c77 | ||
![]() |
ecc9379d7e | ||
![]() |
f8d27342dc | ||
![]() |
17bd2bf2ba | ||
![]() |
d984a3f275 | ||
![]() |
64049bd546 | ||
![]() |
9218091c33 | ||
![]() |
75df7d6413 | ||
![]() |
29341f81e1 | ||
![]() |
68c5a3dca7 | ||
![]() |
b1e2e370c8 | ||
![]() |
ba9d3afc88 | ||
![]() |
9a0434be32 | ||
![]() |
d6923a2ff0 | ||
![]() |
8f7f6dc19e | ||
![]() |
70b8817643 | ||
![]() |
87c25cbbfe | ||
![]() |
082e905014 | ||
![]() |
e3e598b208 | ||
![]() |
6cf92d4ea6 | ||
![]() |
89ad685f3a | ||
![]() |
1e868dc802 | ||
![]() |
58fcfd9a34 | ||
![]() |
c849afbc11 | ||
![]() |
2ebe71ddd0 | ||
![]() |
7a8e8c1f3e | ||
![]() |
5e897ad1c2 | ||
![]() |
ce07907f85 | ||
![]() |
1e38aa7b53 | ||
![]() |
3bc0c23e5a | ||
![]() |
f07b6d105a | ||
![]() |
46491269e3 | ||
![]() |
8d9c43af6a | ||
![]() |
c3568354aa | ||
![]() |
e68696ccd4 | ||
![]() |
35d8b2e790 | ||
![]() |
1f83573206 | ||
![]() |
2887e76514 | ||
![]() |
63b9943203 | ||
![]() |
bd5aedd83f | ||
![]() |
c9ff6d7bb9 | ||
![]() |
5835def5d0 | ||
![]() |
6a2694ce3b | ||
![]() |
65ae7bce79 | ||
![]() |
57ce8da0ee | ||
![]() |
7484bf7403 | ||
![]() |
f6b2312c49 | ||
![]() |
6027cb7cb0 | ||
![]() |
c1740aae6c | ||
![]() |
22ce29e86c | ||
![]() |
251d1b5b2e | ||
![]() |
ea64708c69 | ||
![]() |
209ec27a29 | ||
![]() |
ceee495525 | ||
![]() |
751a2347aa | ||
![]() |
efd96d5fdf | ||
![]() |
2ee5af8bfb | ||
![]() |
a4c0edf493 | ||
![]() |
28c8be97b6 | ||
![]() |
f320a44d45 | ||
![]() |
c0cc6cc176 | ||
![]() |
be62c09d06 | ||
![]() |
5fe3546d2a | ||
![]() |
c4b2ea125c | ||
![]() |
3301b038fe | ||
![]() |
6a58d1e3da | ||
![]() |
e5670d820d | ||
![]() |
1870aead73 | ||
![]() |
95db6cca2c | ||
![]() |
79a792ac62 | ||
![]() |
6406f9eb86 | ||
![]() |
8f8ec496f8 | ||
![]() |
3d6847a3a2 | ||
![]() |
613abaec1b | ||
![]() |
b51ae9ac38 | ||
![]() |
c4fd03542b | ||
![]() |
e40d6c5ef0 | ||
![]() |
9f7dee3baa | ||
![]() |
d3da62c04a | ||
![]() |
aa7bb3f8c9 | ||
![]() |
3a9dfe339a | ||
![]() |
08830003a3 | ||
![]() |
efeeb6cb02 | ||
![]() |
ac59ce2deb | ||
![]() |
3ff89a03ac | ||
![]() |
25e231cd7c | ||
![]() |
f4d8d909b0 | ||
![]() |
35ea18a117 | ||
![]() |
ee9433261b | ||
![]() |
1cb48c7760 | ||
![]() |
172bf4bd51 | ||
![]() |
fe1039cfbc | ||
![]() |
a9de79546b | ||
![]() |
7afdf43872 | ||
![]() |
bfc7d5d0dd | ||
![]() |
690832052a | ||
![]() |
f7c1a47d52 | ||
![]() |
930843d065 | ||
![]() |
a9ee609502 | ||
![]() |
9fd2125923 | ||
![]() |
ede8703f9d | ||
![]() |
6d85623d9b | ||
![]() |
6236ee8f6d | ||
![]() |
92665d80d6 | ||
![]() |
70ae0bc77e | ||
![]() |
e28c757352 | ||
![]() |
7cdcf7ebab | ||
![]() |
7c368af5ed | ||
![]() |
df2fbbabc6 | ||
![]() |
48141eb199 | ||
![]() |
343fbb282c | ||
![]() |
cea9ed056b | ||
![]() |
73bd90c555 | ||
![]() |
917de46c45 | ||
![]() |
b592092923 | ||
![]() |
760bc1c22a | ||
![]() |
1f8b81ee78 | ||
![]() |
70af7d05e3 | ||
![]() |
14c04ee4ac | ||
![]() |
83815d3caa | ||
![]() |
487c621fa5 | ||
![]() |
304eab801c | ||
![]() |
dfe3add1cc | ||
![]() |
a32bf5539e | ||
![]() |
e944333e5f | ||
![]() |
dad23a52b0 | ||
![]() |
53606c306d | ||
![]() |
53b03f8231 | ||
![]() |
ac5f6dc024 | ||
![]() |
2c15e0dd32 | ||
![]() |
007334fef0 | ||
![]() |
b3ae2d2748 | ||
![]() |
7149c766d0 | ||
![]() |
5dca0191d2 | ||
![]() |
356668a68d | ||
![]() |
63c9442126 | ||
![]() |
0031d7911d | ||
![]() |
31c92b43b4 | ||
![]() |
2667548041 | ||
![]() |
54282ba7e0 | ||
![]() |
7f324abd44 | ||
![]() |
65a0c3b40a | ||
![]() |
2449baac5b | ||
![]() |
0ab03e1856 | ||
![]() |
9a3f85106c | ||
![]() |
42c495d8ab | ||
![]() |
58def585f1 | ||
![]() |
047034d831 | ||
![]() |
bdb2f24a81 | ||
![]() |
636870a075 | ||
![]() |
8981174302 | ||
![]() |
dd5f05334b | ||
![]() |
929b626b51 | ||
![]() |
7d5b2ec81e | ||
![]() |
f0e2d36c34 | ||
![]() |
94f171d757 | ||
![]() |
04e06db430 | ||
![]() |
d74b215169 | ||
![]() |
404ea94dd2 | ||
![]() |
4afe7c6b46 | ||
![]() |
60b20c4d01 | ||
![]() |
58658c6b1a | ||
![]() |
ec444317b3 | ||
![]() |
8b4aee1afa | ||
![]() |
51abd74304 | ||
![]() |
b93b465f09 | ||
![]() |
5aad68ec62 | ||
![]() |
74fbc937a1 | ||
![]() |
7e35f544eb | ||
![]() |
ed1c3cffc1 | ||
![]() |
c4983a9f9b | ||
![]() |
5b43262e7a | ||
![]() |
dad4408679 | ||
![]() |
a78c4d12b4 | ||
![]() |
74664a9df8 | ||
![]() |
fce5281a03 | ||
![]() |
de0bd2f486 | ||
![]() |
079fb5d108 | ||
![]() |
1c7435a32b | ||
![]() |
1afd374cf6 | ||
![]() |
3adf549915 | ||
![]() |
e94d669eca | ||
![]() |
5fac0b4689 | ||
![]() |
832d323a6e | ||
![]() |
03f1dbd5b2 | ||
![]() |
c0a216f109 | ||
![]() |
ad67b13270 | ||
![]() |
5d420c08c6 | ||
![]() |
3d8235c670 | ||
![]() |
5a209f81d1 | ||
![]() |
d17d8e2805 | ||
![]() |
ca7636e7bc | ||
![]() |
532cfc10d0 | ||
![]() |
72d68c4377 | ||
![]() |
00f5964aa4 | ||
![]() |
bd1ad5fa56 | ||
![]() |
f2e22e7445 | ||
![]() |
fcf345abab | ||
![]() |
24ad43d3e4 | ||
![]() |
9a7cdf42e1 | ||
![]() |
c36b652d5b | ||
![]() |
553070fc23 | ||
![]() |
5d69f7e24f | ||
![]() |
bc0e2bada0 | ||
![]() |
80b6cc1d94 | ||
![]() |
bce3273e64 | ||
![]() |
3abf61152a | ||
![]() |
14923d4cd6 | ||
![]() |
6fdc4bf900 | ||
![]() |
d21e1f75b5 | ||
![]() |
84a0b37fcc | ||
![]() |
f135a0f09e | ||
![]() |
0f24c99456 | ||
![]() |
9eae0ab947 | ||
![]() |
3bf1f79c79 | ||
![]() |
b21074c871 | ||
![]() |
d7893d9a32 | ||
![]() |
9cbdda330c | ||
![]() |
42a9bfd099 | ||
![]() |
eb15bd01ca | ||
![]() |
9e98aebeb3 | ||
![]() |
1361cbc826 | ||
![]() |
679d0808a9 | ||
![]() |
6fe9a548ad | ||
![]() |
2d6d2430d2 | ||
![]() |
a445538e81 | ||
![]() |
50d38ffbd8 | ||
![]() |
93bcdfd9c9 | ||
![]() |
5be3b101a5 | ||
![]() |
024c7476c7 | ||
![]() |
30a7ffe93d | ||
![]() |
e2d803ebf7 | ||
![]() |
be7e67c940 | ||
![]() |
ead4b13ba5 | ||
![]() |
e02c42ee18 | ||
![]() |
d39886fdf8 | ||
![]() |
11a425f1de | ||
![]() |
f0e194e584 | ||
![]() |
d4b9331cf2 | ||
![]() |
37e1acc5f1 | ||
![]() |
ffaf6a577d | ||
![]() |
afdaf6ba39 | ||
![]() |
4c49367910 | ||
![]() |
a506c4411d | ||
![]() |
1859c9854e | ||
![]() |
6ff29b9ae6 | ||
![]() |
3578f6b849 | ||
![]() |
0347864fde | ||
![]() |
5f9786a2c7 | ||
![]() |
75aeff1898 | ||
![]() |
0afcdce6d3 | ||
![]() |
a591d0ea87 | ||
![]() |
0e111a3532 | ||
![]() |
b599466ffa | ||
![]() |
69727e78df | ||
![]() |
02ae67b147 | ||
![]() |
a769f78801 | ||
![]() |
d583e42428 | ||
![]() |
da732becb6 | ||
![]() |
b89a4d58d9 | ||
![]() |
09854147d1 | ||
![]() |
3648c2bfe3 | ||
![]() |
3f3ee032f6 | ||
![]() |
68e5d54331 | ||
![]() |
824c434b0b | ||
![]() |
9f0e0ca656 | ||
![]() |
95f89ba03e | ||
![]() |
697f72ecf4 | ||
![]() |
4f03f2ab51 | ||
![]() |
c81531cb7a | ||
![]() |
7b6e4aa153 | ||
![]() |
f21039d19d | ||
![]() |
8c936a91be | ||
![]() |
24451892ff | ||
![]() |
6bba2c82fe | ||
![]() |
3320dc6bc4 | ||
![]() |
9d42fd9293 | ||
![]() |
e6b806616f | ||
![]() |
6ec5872391 | ||
![]() |
a26cf932a1 | ||
![]() |
38a3e3ab9f | ||
![]() |
32b17c1418 | ||
![]() |
44aa6a1579 | ||
![]() |
2369aacd2a | ||
![]() |
7dafc6364b | ||
![]() |
3d25fa0aeb | ||
![]() |
0297b0f296 | ||
![]() |
4c7d09c3d8 | ||
![]() |
48a74826e8 | ||
![]() |
ef34068ac4 | ||
![]() |
3987a8db77 | ||
![]() |
c413ab030b | ||
![]() |
0f39007f92 | ||
![]() |
ab811daba7 | ||
![]() |
1428bf8ffa | ||
![]() |
53f7f38e23 | ||
![]() |
ce6ad9e0d3 | ||
![]() |
17a0c6123a | ||
![]() |
10edc5a8ad | ||
![]() |
bdf07d6bd5 | ||
![]() |
10ff65e71a | ||
![]() |
9395097fda | ||
![]() |
47eb0e00e3 | ||
![]() |
57d5f34ac5 | ||
![]() |
06b040412a | ||
![]() |
cab040c74a | ||
![]() |
21706a7d15 | ||
![]() |
22e4b8aaeb | ||
![]() |
bd43a6021a | ||
![]() |
a90b58b6db | ||
![]() |
92a9b096ec | ||
![]() |
7a6aa99840 | ||
![]() |
5657f0d793 | ||
![]() |
798529007e | ||
![]() |
953c5a5b5b | ||
![]() |
4313265c00 | ||
![]() |
9405f267ba | ||
![]() |
1d29238199 | ||
![]() |
c5bf66f462 | ||
![]() |
e6180bdfaa | ||
![]() |
55c391afc8 | ||
![]() |
782fa67320 | ||
![]() |
1e3ab75bb7 | ||
![]() |
5f6dd12a73 | ||
![]() |
d18c06d2c4 | ||
![]() |
baf99a9cfe | ||
![]() |
159931a6ea | ||
![]() |
7831f2925b | ||
![]() |
8fcb7840de | ||
![]() |
9ece9461dc | ||
![]() |
b304acaaba | ||
![]() |
5a1960609a | ||
![]() |
476aa6e3aa | ||
![]() |
aa76007fd0 | ||
![]() |
17a8813c4b | ||
![]() |
fe79fc9003 | ||
![]() |
ac8649ac18 | ||
![]() |
817db026b1 | ||
![]() |
01ef97949b | ||
![]() |
9e151051a8 | ||
![]() |
0feab23eb6 | ||
![]() |
8957f83939 | ||
![]() |
3bf2bc94f3 | ||
![]() |
8c3d5f1f4b | ||
![]() |
124e6e0811 | ||
![]() |
54d9d17d14 | ||
![]() |
62bdb7fb6d | ||
![]() |
af6574fdf1 | ||
![]() |
115a993cd2 | ||
![]() |
188b6c2fe5 | ||
![]() |
99adb2ceda | ||
![]() |
6344a3bcef | ||
![]() |
f62c17de0b | ||
![]() |
9d269b8111 | ||
![]() |
49b26a9640 | ||
![]() |
dddb79eeb2 | ||
![]() |
b0909ecb98 | ||
![]() |
ec63ce0532 | ||
![]() |
1eb03e3afb | ||
![]() |
26d906f1b7 | ||
![]() |
af9ceac0b4 | ||
![]() |
911159a14f | ||
![]() |
6a639e4ac9 | ||
![]() |
c4a6c86a69 | ||
![]() |
d2bac9d8d1 | ||
![]() |
758aa50550 | ||
![]() |
1af887cee7 | ||
![]() |
34b115c694 | ||
![]() |
8e4935409f | ||
![]() |
ca32d221c4 | ||
![]() |
e2de1fe038 | ||
![]() |
80179062db | ||
![]() |
7143620d25 | ||
![]() |
a0e51e2a7e | ||
![]() |
53dbd01cb1 | ||
![]() |
3607c16fd1 | ||
![]() |
4dfdd01295 | ||
![]() |
8983a3c581 | ||
![]() |
9cddef9108 | ||
![]() |
3dbea953a7 | ||
![]() |
962bbc04b5 | ||
![]() |
0804026d74 | ||
![]() |
879aa1f9f8 | ||
![]() |
9ffe2c14df | ||
![]() |
f0d2f07193 | ||
![]() |
ccf5928262 | ||
![]() |
b2f8634008 | ||
![]() |
43dba351c3 | ||
![]() |
b95478b635 | ||
![]() |
523f8a8951 | ||
![]() |
0f71924d06 | ||
![]() |
6d1bfc0be0 | ||
![]() |
b0f53268f6 | ||
![]() |
75f5db23df | ||
![]() |
85141812d9 | ||
![]() |
8819ddefa7 | ||
![]() |
b693c12500 | ||
![]() |
cbe3db8187 | ||
![]() |
7bccbc9471 | ||
![]() |
a452520f1a | ||
![]() |
a331b34b49 | ||
![]() |
8d6f0f8e9e | ||
![]() |
47dd5a1949 | ||
![]() |
387f8fd44c | ||
![]() |
b69b1f6f67 | ||
![]() |
d1427ffd54 | ||
![]() |
00c876dd93 | ||
![]() |
9d1aa9e59a | ||
![]() |
aceebba99a | ||
![]() |
7d6a8c4607 | ||
![]() |
5fd90355ae | ||
![]() |
9b01a2a4da | ||
![]() |
a2986d70a0 | ||
![]() |
5dfa38ca99 | ||
![]() |
157c6812cc | ||
![]() |
215ff4b74a | ||
![]() |
ca7b8b865a | ||
![]() |
fd0b12f6a1 | ||
![]() |
2f3b739f9e | ||
![]() |
3255ddca63 | ||
![]() |
b5460712e6 | ||
![]() |
6f7dcc2b6e | ||
![]() |
70d4800cb1 | ||
![]() |
a8b85cdb0d | ||
![]() |
fe10523972 | ||
![]() |
c975a56245 | ||
![]() |
d070e976b0 | ||
![]() |
0caf6bfabb | ||
![]() |
b842d7938f | ||
![]() |
cebbf84375 | ||
![]() |
8608431490 | ||
![]() |
78ba18b176 | ||
![]() |
f8c30c8526 | ||
![]() |
693c9b85a5 | ||
![]() |
70bb7defd1 | ||
![]() |
160377ca31 | ||
![]() |
2c0ce77a4e | ||
![]() |
77fbb0c9da | ||
![]() |
5971425d23 | ||
![]() |
aefff5c861 | ||
![]() |
a296b5e645 | ||
![]() |
eb486a3a07 | ||
![]() |
062b8521ba | ||
![]() |
1b07f3195a | ||
![]() |
dfa7d4cb8d | ||
![]() |
a14dd9666c | ||
![]() |
b07bd4374f | ||
![]() |
b4e12b0ea8 | ||
![]() |
ee5c17bb85 | ||
![]() |
16c9d3400c | ||
![]() |
4dd994348d | ||
![]() |
f0cbfafc24 | ||
![]() |
d3f38f5488 | ||
![]() |
737090a67a | ||
![]() |
4f66a4d090 | ||
![]() |
df54f909c1 | ||
![]() |
772b195eca | ||
![]() |
87866e34ed | ||
![]() |
c98ac05097 | ||
![]() |
36f991b6f9 | ||
![]() |
a81c5164fc | ||
![]() |
5942482690 | ||
![]() |
4f538ca2fc | ||
![]() |
9f2281a3e2 | ||
![]() |
b0d2f28c78 | ||
![]() |
d4380a4426 | ||
![]() |
ae2738d4cc | ||
![]() |
aa5ae028b2 | ||
![]() |
7ab8c76aa0 | ||
![]() |
8075b65e14 | ||
![]() |
073ce3bf1b | ||
![]() |
80fcbfe01b | ||
![]() |
dba0041e5f | ||
![]() |
b8a44afd25 | ||
![]() |
e2445bf585 | ||
![]() |
50706c524e | ||
![]() |
11e0cb9398 | ||
![]() |
1e82e40802 | ||
![]() |
ff00644e62 | ||
![]() |
97bcd3792b | ||
![]() |
5738a09771 | ||
![]() |
c461cc4878 | ||
![]() |
878fab347a | ||
![]() |
354b331b08 | ||
![]() |
3b9aadb90f | ||
![]() |
7193d018ce | ||
![]() |
d5cea034ac | ||
![]() |
a2760c10b3 | ||
![]() |
5492fae213 | ||
![]() |
490a23ae0a | ||
![]() |
3593cf3808 | ||
![]() |
6ea7400ff4 | ||
![]() |
1a4ba35ef4 | ||
![]() |
2d52cab693 | ||
![]() |
e1fac78aba | ||
![]() |
e79fc9cae4 | ||
![]() |
9200e1011b | ||
![]() |
373d29eeab | ||
![]() |
bc337c588a | ||
![]() |
112b05f7ad | ||
![]() |
9f84af95f6 | ||
![]() |
0873cfa997 | ||
![]() |
94d7162782 | ||
![]() |
5db62679fa | ||
![]() |
a4a0102679 | ||
![]() |
2afcfbb4bc | ||
![]() |
f0e8f070a8 | ||
![]() |
c42374e031 | ||
![]() |
be610c7fa9 | ||
![]() |
4ff824663b | ||
![]() |
1581b5ac0a | ||
![]() |
5fb48ed54b | ||
![]() |
903e9e6093 | ||
![]() |
d30e491817 | ||
![]() |
aa727e3260 | ||
![]() |
1cad3a7149 | ||
![]() |
3b7f6740bb | ||
![]() |
2febc5efad | ||
![]() |
903616bef6 | ||
![]() |
c944193fb4 | ||
![]() |
4f2155ea63 | ||
![]() |
4bda1edda7 | ||
![]() |
1a55cc8604 | ||
![]() |
bf7ab475ee | ||
![]() |
2f39efb935 | ||
![]() |
9f8eb985e4 | ||
![]() |
3549fef71c | ||
![]() |
2cfa64c2a3 | ||
![]() |
7245a0a599 | ||
![]() |
0633da3244 | ||
![]() |
96341976f5 | ||
![]() |
9abfaec4d5 | ||
![]() |
945c52dd6b | ||
![]() |
6567d24760 | ||
![]() |
ffaf9b6e0c | ||
![]() |
463e6908b1 | ||
![]() |
e185ceb385 | ||
![]() |
1b21bbe5b7 | ||
![]() |
14b7053ed8 | ||
![]() |
2760526def | ||
![]() |
d851db22d0 | ||
![]() |
2fa360e400 | ||
![]() |
e4eb146169 | ||
![]() |
86611453b5 | ||
![]() |
65f9d1b6b9 | ||
![]() |
2fceaf2cf4 | ||
![]() |
d82b50fcdb | ||
![]() |
ab6e49bf4f | ||
![]() |
ee90422f56 | ||
![]() |
627184f124 | ||
![]() |
fa02edfefc | ||
![]() |
61afebc827 | ||
![]() |
a4c22799e7 | ||
![]() |
870a110a75 | ||
![]() |
8c859f9408 | ||
![]() |
0a36101da1 | ||
![]() |
c1bf063b12 | ||
![]() |
0da56a800d | ||
![]() |
4d2172d153 | ||
![]() |
d0fab0e1f1 | ||
![]() |
4cedbdbc60 | ||
![]() |
a82d34cbce | ||
![]() |
16d0c243c7 | ||
![]() |
855901bd9e | ||
![]() |
6be8a581d2 | ||
![]() |
655deb12c8 | ||
![]() |
31b1b9457b | ||
![]() |
c4394228f2 | ||
![]() |
98e6dbe141 | ||
![]() |
4fac1ef7c4 | ||
![]() |
034bc6a79e | ||
![]() |
94e64676af | ||
![]() |
3a638220af | ||
![]() |
0772308bf5 | ||
![]() |
9f5ea80731 | ||
![]() |
fb1f520096 | ||
![]() |
dd36609443 | ||
![]() |
4eace3fb7e | ||
![]() |
f13e93e2ce | ||
![]() |
da4f8ab529 | ||
![]() |
b6ff4ec992 | ||
![]() |
25f6cac69a | ||
![]() |
172a8934e3 | ||
![]() |
aead014bcf | ||
![]() |
59770c80db | ||
![]() |
6d6b77148d | ||
![]() |
c1e8f5765f | ||
![]() |
76e442940b | ||
![]() |
b2205097da | ||
![]() |
14886d42e8 | ||
![]() |
2f35403078 | ||
![]() |
961d55a1c6 | ||
![]() |
0fca0ef734 | ||
![]() |
cc3acd81bc | ||
![]() |
69a691c19e | ||
![]() |
e0a4f5c9c9 | ||
![]() |
cabf9b8fb8 | ||
![]() |
61e24da07d | ||
![]() |
648511dfad | ||
![]() |
dfc9efc31a | ||
![]() |
b58a22addc | ||
![]() |
e07a9eeda2 | ||
![]() |
f0de42fa63 | ||
![]() |
0121661ad0 | ||
![]() |
fb6e46bd69 | ||
![]() |
9bd041799b | ||
![]() |
9e191c4ed9 | ||
![]() |
794b4cf26a | ||
![]() |
2d8421943f | ||
![]() |
48dc2312d9 | ||
![]() |
cdf7a1adc4 | ||
![]() |
99ebd12081 | ||
![]() |
859337b5c1 | ||
![]() |
1e601559a0 | ||
![]() |
9314cba724 | ||
![]() |
25224f0308 | ||
![]() |
9a981d5373 | ||
![]() |
c7116361ab | ||
![]() |
1f17236c4f | ||
![]() |
67f415de35 | ||
![]() |
b890150817 | ||
![]() |
4de1fc49df | ||
![]() |
fb80d5d70d | ||
![]() |
eaa25e412a | ||
![]() |
706142f98e | ||
![]() |
36a72d0a32 | ||
![]() |
1d44d387ac | ||
![]() |
f93c41f2d8 | ||
![]() |
5b13f880c8 | ||
![]() |
c4af873036 | ||
![]() |
fe820fb4fe | ||
![]() |
82ad0735d2 | ||
![]() |
8b0a421924 | ||
![]() |
d44cb3d92e | ||
![]() |
5f335ef5b3 | ||
![]() |
5831bf9653 | ||
![]() |
2480dfbb6d | ||
![]() |
162b352ea5 | ||
![]() |
9f30d7d7ba | ||
![]() |
27c296bb89 | ||
![]() |
ef67908451 | ||
![]() |
3dedc3bfc7 | ||
![]() |
65d509c97f | ||
![]() |
11f38c4d3a | ||
![]() |
3bca6497f7 | ||
![]() |
23d79b9265 | ||
![]() |
12c53a3d4d | ||
![]() |
1dfe58ec02 | ||
![]() |
7615e62bbc | ||
![]() |
53189a6487 | ||
![]() |
027b11c3fb | ||
![]() |
453ab7de66 | ||
![]() |
58f8ded161 | ||
![]() |
497ce2e84f | ||
![]() |
3794c6f508 | ||
![]() |
ee6c9fa5d4 | ||
![]() |
1369bb095f | ||
![]() |
c786d7549a | ||
![]() |
b59840cb77 | ||
![]() |
265d57d8b7 | ||
![]() |
5eed84f9e5 | ||
![]() |
0a334dff1d | ||
![]() |
5fff9bdc02 | ||
![]() |
a9fd261bab | ||
![]() |
ef087be4f0 | ||
![]() |
2099978b8f | ||
![]() |
712bee297a | ||
![]() |
57bba90091 | ||
![]() |
d877f5c764 | ||
![]() |
4c66cc1e33 | ||
![]() |
27a3edeb93 | ||
![]() |
f79fc29203 | ||
![]() |
174240a220 | ||
![]() |
60d8af5c16 | ||
![]() |
627a5892f1 | ||
![]() |
7767f6d9cc | ||
![]() |
9729fd6b15 | ||
![]() |
17916f29f6 | ||
![]() |
584b9323ec | ||
![]() |
82c1aadfa9 | ||
![]() |
bd497af89b | ||
![]() |
1683c5630a | ||
![]() |
b5df1a026a | ||
![]() |
b290c32aeb | ||
![]() |
a29b3c6db4 | ||
![]() |
ffb2f4f5db | ||
![]() |
24c95f4801 | ||
![]() |
1aaec2d555 | ||
![]() |
fedb198ae7 | ||
![]() |
add654ccac | ||
![]() |
5a578643a6 | ||
![]() |
f0712bd213 | ||
![]() |
f490632722 | ||
![]() |
2610b96762 | ||
![]() |
8d90cb834d | ||
![]() |
9d92509796 | ||
![]() |
04a78ee0ba | ||
![]() |
3703390268 | ||
![]() |
4ab6415f49 | ||
![]() |
9228722147 | ||
![]() |
0e5529b4ca | ||
![]() |
4d454ec932 | ||
![]() |
a9282ad118 | ||
![]() |
0e959641af | ||
![]() |
13263eea76 | ||
![]() |
1a3418de58 | ||
![]() |
4402995132 | ||
![]() |
4e33f9875b | ||
![]() |
ed2893e37f | ||
![]() |
c35be241ca | ||
![]() |
aad0b4ddfe | ||
![]() |
52f5c7ddb5 | ||
![]() |
a04b933161 | ||
![]() |
c77e12edbb | ||
![]() |
108bd04cf8 | ||
![]() |
a95b500e42 | ||
![]() |
c2744c5569 | ||
![]() |
a1dfd87bbe | ||
![]() |
221aa8687f | ||
![]() |
fa8ac0a8ba | ||
![]() |
cc1f9873cb | ||
![]() |
3cae9ee5d2 | ||
![]() |
f764914adb | ||
![]() |
991250c73f | ||
![]() |
af46cf5ce8 | ||
![]() |
dbb1c42c47 | ||
![]() |
991f593b2e | ||
![]() |
e43c083d50 | ||
![]() |
3cd9bdc1d4 | ||
![]() |
c0b8e6178d | ||
![]() |
410f9d0af5 | ||
![]() |
b1fedf28dc | ||
![]() |
b0df03dcd2 | ||
![]() |
2794e50a19 | ||
![]() |
365ae656f2 | ||
![]() |
98649dcba6 | ||
![]() |
213c8096d2 | ||
![]() |
398938f27e | ||
![]() |
6378e62645 | ||
![]() |
251885d4be | ||
![]() |
f53909355f | ||
![]() |
242b68889a | ||
![]() |
6a66b65f2a | ||
![]() |
f30ead6bcb | ||
![]() |
237ee72ca6 | ||
![]() |
3590d84ad6 | ||
![]() |
2dae8c162d | ||
![]() |
9a192b708e | ||
![]() |
c193f9334f | ||
![]() |
6e682dc752 | ||
![]() |
da86fe56bd | ||
![]() |
45865d701a | ||
![]() |
a66a31b474 | ||
![]() |
2661e7102f | ||
![]() |
224965b91e | ||
![]() |
a9c7375534 | ||
![]() |
e77f7ee0bf | ||
![]() |
ae5dd0cad6 | ||
![]() |
a128907a4e | ||
![]() |
d6453a8ed0 | ||
![]() |
dd1e8240b8 | ||
![]() |
b12f39916f | ||
![]() |
aae88fe1ad | ||
![]() |
83bb400df1 | ||
![]() |
8ea8067788 | ||
![]() |
9fbc9d59f5 | ||
![]() |
b96ba69a72 | ||
![]() |
c4ccab6a5d | ||
![]() |
f84f27bb56 | ||
![]() |
416cc0ffa9 | ||
![]() |
1fd5ec4db6 | ||
![]() |
4795c35c68 | ||
![]() |
25ce63b86d | ||
![]() |
5271033d34 | ||
![]() |
6ba8f33399 | ||
![]() |
7ab79bd815 | ||
![]() |
04a0a847c7 | ||
![]() |
436fa9af69 | ||
![]() |
ca0bbb0f08 | ||
![]() |
88996144a5 | ||
![]() |
44d5eee99e | ||
![]() |
0d1ff6074f | ||
![]() |
d63757634a | ||
![]() |
fd61cf3388 | ||
![]() |
a6a6b63e5a | ||
![]() |
c02c2def29 | ||
![]() |
ff66548462 | ||
![]() |
c9f292e252 | ||
![]() |
18cef5f3bd | ||
![]() |
e19340f1e0 | ||
![]() |
feb613cb6d | ||
![]() |
afa6bdfa44 | ||
![]() |
200e6d9905 | ||
![]() |
70772c49bd | ||
![]() |
762ea97e8b | ||
![]() |
8156b8b356 | ||
![]() |
3a2cbae0a0 | ||
![]() |
0ad8da097b | ||
![]() |
e2dcdd2811 | ||
![]() |
8074f9146b | ||
![]() |
df24bac913 | ||
![]() |
4d4091adcc | ||
![]() |
cac54c41a1 | ||
![]() |
130931d7af | ||
![]() |
d35b08b35e | ||
![]() |
82031da6a6 | ||
![]() |
9df5ee7b11 | ||
![]() |
2ed1a57cd9 | ||
![]() |
101450cba6 | ||
![]() |
6bab5b3f7c | ||
![]() |
ca3c0e00a7 | ||
![]() |
6d64daf324 | ||
![]() |
9ae4578e19 | ||
![]() |
e06b7ab87a | ||
![]() |
1e2adedcbf | ||
![]() |
adf763c1b0 | ||
![]() |
36ee0df256 | ||
![]() |
823d85b24a | ||
![]() |
a3b3038709 | ||
![]() |
ddeb18f626 | ||
![]() |
90cd11bd38 | ||
![]() |
e9ba37b8de | ||
![]() |
d5e4a1b1ad | ||
![]() |
129e6d60e5 | ||
![]() |
4b77f2f590 | ||
![]() |
a909966562 | ||
![]() |
fd184239d6 | ||
![]() |
52bc49dc6a | ||
![]() |
b9352ccc06 | ||
![]() |
525b2baf06 | ||
![]() |
a8edeb2459 | ||
![]() |
e3830d64e0 | ||
![]() |
91f3e2c2b4 | ||
![]() |
77b4408416 | ||
![]() |
cede96f018 | ||
![]() |
8e0a28d238 | ||
![]() |
da5d594428 | ||
![]() |
9f9ee0bb58 | ||
![]() |
163aca6179 | ||
![]() |
cb06d3b0ae | ||
![]() |
dbe18dd100 | ||
![]() |
217970667a | ||
![]() |
dace794167 | ||
![]() |
590780a539 | ||
![]() |
cbd1f47e87 | ||
![]() |
f89cff4e4a | ||
![]() |
cb08e0bf9f | ||
![]() |
3b54b29a99 | ||
![]() |
25983e046c | ||
![]() |
a6a124d2e6 | ||
![]() |
c7e1d30553 | ||
![]() |
6cc8c45634 | ||
![]() |
ee9a9114b7 | ||
![]() |
11f00f866c | ||
![]() |
03ea61ba81 | ||
![]() |
f6c500c998 | ||
![]() |
b590f0f98f | ||
![]() |
ef9359b208 | ||
![]() |
efd243a340 | ||
![]() |
bafb8b86db | ||
![]() |
84b701747f | ||
![]() |
ec42446daa | ||
![]() |
5046c4c911 | ||
![]() |
ce8c9906cb | ||
![]() |
6fb5482bba | ||
![]() |
58189963f5 | ||
![]() |
f488a71304 | ||
![]() |
4b706e004d | ||
![]() |
40e10cc270 | ||
![]() |
41db227eb3 | ||
![]() |
43eea965c5 | ||
![]() |
8101c9f0bc | ||
![]() |
b4cda90338 | ||
![]() |
7ca37c412e | ||
![]() |
e4e3356dc9 | ||
![]() |
0deaa03218 | ||
![]() |
a7104c41a2 | ||
![]() |
5176b8c322 | ||
![]() |
c37c70446d | ||
![]() |
63abc8a2c8 | ||
![]() |
ba5c038e3b | ||
![]() |
a6669415f5 | ||
![]() |
4086fad867 | ||
![]() |
8a71c13078 | ||
![]() |
5d77f64e76 | ||
![]() |
0d092b977f | ||
![]() |
69582ff83d | ||
![]() |
a5c7da331a | ||
![]() |
8e842296b7 | ||
![]() |
7db14d1df7 | ||
![]() |
067ec2eb9c | ||
![]() |
2d332b32d9 | ||
![]() |
1d9ad2ba86 | ||
![]() |
a28e2177f7 | ||
![]() |
18fe0df691 | ||
![]() |
8e21a06d99 | ||
![]() |
2daf5473bb | ||
![]() |
928ff53adf | ||
![]() |
a71e95e6e5 | ||
![]() |
cb4a54b5cc | ||
![]() |
37e4524156 | ||
![]() |
9ac24ee051 | ||
![]() |
f28ccd559a | ||
![]() |
8e84a93d8e | ||
![]() |
d871dec1b7 | ||
![]() |
b133e1a197 | ||
![]() |
9346a037b9 | ||
![]() |
89facbcddd | ||
![]() |
53fef35638 | ||
![]() |
bfe496a09b | ||
![]() |
ff774c2e8e | ||
![]() |
08a6d1078c | ||
![]() |
e9bcc919bf | ||
![]() |
04f4693c85 | ||
![]() |
2a58a0a4c4 | ||
![]() |
d911843648 | ||
![]() |
c80d178410 | ||
![]() |
9fb4dca39b | ||
![]() |
0dd444d50b | ||
![]() |
f3bf418997 | ||
![]() |
676027245f | ||
![]() |
6c5039f1ba | ||
![]() |
807be59f25 | ||
![]() |
8e9896ec2e | ||
![]() |
110c2dbac8 | ||
![]() |
f55ec4bd8a | ||
![]() |
06c9bf420e | ||
![]() |
3c9bc53a79 | ||
![]() |
de7a35dfe9 | ||
![]() |
92638c2e97 | ||
![]() |
63251e6a9a | ||
![]() |
59844c33fd | ||
![]() |
d36dd2ece1 | ||
![]() |
1fdb94739b | ||
![]() |
8a18f4c44f | ||
![]() |
c9c47c5519 | ||
![]() |
6be8b55daa | ||
![]() |
f2dc2f5530 | ||
![]() |
42842e7aec | ||
![]() |
49d9f77d1b | ||
![]() |
d06a89564f | ||
![]() |
58a8510d49 | ||
![]() |
8055d6555e | ||
![]() |
39620d3510 | ||
![]() |
6d19711926 | ||
![]() |
0b362dd435 | ||
![]() |
9485731e7d | ||
![]() |
1449fb0f84 | ||
![]() |
e548dd49ca | ||
![]() |
337d22bbf4 | ||
![]() |
35863ee6e9 | ||
![]() |
0784a2d4d0 | ||
![]() |
75d5c0e356 | ||
![]() |
a2dd6d76a8 | ||
![]() |
bdc6b59857 | ||
![]() |
34e95f1e89 | ||
![]() |
6a92cfc573 | ||
![]() |
9f759d70b6 | ||
![]() |
43e957e8d3 | ||
![]() |
ae316f60e4 | ||
![]() |
1ac423ba56 | ||
![]() |
b43490dd76 | ||
![]() |
f586e81dd1 | ||
![]() |
8a6d8a7d8c | ||
![]() |
45d607f1a0 | ||
![]() |
d84abaa229 | ||
![]() |
3fd1d4d9b3 | ||
![]() |
078b8efb56 | ||
![]() |
5066995f72 | ||
![]() |
577fe3dba8 | ||
![]() |
d96f4999bc | ||
![]() |
6e80ff4eb6 | ||
![]() |
3f8f022d48 | ||
![]() |
93a2e2151e | ||
![]() |
663a1ed9d4 | ||
![]() |
4f46c55c85 | ||
![]() |
9701c98af9 | ||
![]() |
aabf2a1c79 | ||
![]() |
29539b090e | ||
![]() |
1c80677ac3 | ||
![]() |
ad419855e9 | ||
![]() |
30b75943f3 | ||
![]() |
41a67b402d | ||
![]() |
caa104b1cc | ||
![]() |
94085f2bc8 | ||
![]() |
d39c962314 | ||
![]() |
706fb0f063 | ||
![]() |
b9d89b040f | ||
![]() |
41421b849a | ||
![]() |
324375da93 | ||
![]() |
536446faf6 | ||
![]() |
d026ac09f3 | ||
![]() |
88c93ac992 | ||
![]() |
d540322d8b | ||
![]() |
ad4db5e936 | ||
![]() |
25cb4d90f3 | ||
![]() |
6c14a353ef | ||
![]() |
74d7d1aa98 | ||
![]() |
43b0d9ed29 | ||
![]() |
3572e6f65a | ||
![]() |
d23d5d2da0 | ||
![]() |
183b9b0d88 | ||
![]() |
7a1af268ae | ||
![]() |
f879b3c5b0 | ||
![]() |
40be72cf65 | ||
![]() |
a8886571d1 | ||
![]() |
1fcd51ea26 | ||
![]() |
89752138be | ||
![]() |
f29ccace2a | ||
![]() |
0c8343e76f | ||
![]() |
9776c9f5a4 | ||
![]() |
a5dbac9817 | ||
![]() |
bad5e0b855 | ||
![]() |
8e4ca55560 | ||
![]() |
f52afc1fe0 | ||
![]() |
815e64302e | ||
![]() |
07b2b18a4e | ||
![]() |
69eca33de7 | ||
![]() |
ec76a480d0 | ||
![]() |
a8823c3ed0 | ||
![]() |
1f1b3a341c | ||
![]() |
8c164a3852 | ||
![]() |
dcf526d810 | ||
![]() |
2fc6d680a0 | ||
![]() |
f414972f33 | ||
![]() |
69d192d989 | ||
![]() |
6c8769e598 | ||
![]() |
c12703422c | ||
![]() |
c0171e1cd1 | ||
![]() |
920a983146 | ||
![]() |
7ec86bfef1 | ||
![]() |
d8bc318688 | ||
![]() |
600ea1848f | ||
![]() |
a3ce9c7662 | ||
![]() |
44ce7577c6 | ||
![]() |
8c3e42f7eb | ||
![]() |
b8887c506c | ||
![]() |
6c4228b7b8 | ||
![]() |
9b1da98386 | ||
![]() |
1615169a3d | ||
![]() |
df83aa4d15 | ||
![]() |
142b96beb0 | ||
![]() |
18d07dd3b9 | ||
![]() |
270039d211 | ||
![]() |
fcf3a480be | ||
![]() |
80ee974973 | ||
![]() |
48c6a38205 | ||
![]() |
c9536e58cb | ||
![]() |
a16d783302 | ||
![]() |
b837aecb27 | ||
![]() |
532405048f | ||
![]() |
6cd0427ff8 | ||
![]() |
d2163f180e | ||
![]() |
d1344457dd | ||
![]() |
596be24d92 | ||
![]() |
4e182b49f9 | ||
![]() |
51ac37f1de | ||
![]() |
b578e88d46 | ||
![]() |
9cb4607f69 | ||
![]() |
2c4d602028 | ||
![]() |
a0feb7f309 | ||
![]() |
77e29050c8 | ||
![]() |
df55f746ca | ||
![]() |
2739d2297f | ||
![]() |
dc734b04d8 | ||
![]() |
d4c542168c | ||
![]() |
94a3b66130 | ||
![]() |
c8baf9b0d7 | ||
![]() |
c4dc0509c2 | ||
![]() |
3bfc428dfe | ||
![]() |
026b7da6f8 | ||
![]() |
58352b9f33 | ||
![]() |
59faf593be | ||
![]() |
4aa41773c4 | ||
![]() |
20c5d6aee1 | ||
![]() |
d503dbc103 | ||
![]() |
53624a6379 | ||
![]() |
5b881db19f | ||
![]() |
dece070d28 | ||
![]() |
8be2ed0034 | ||
![]() |
37c6b57a48 | ||
![]() |
8a0b5c24a5 | ||
![]() |
20431da757 | ||
![]() |
f219c87a9e | ||
![]() |
5d2134db56 | ||
![]() |
1dc46dd31c | ||
![]() |
96250ce9a0 | ||
![]() |
691682e09c | ||
![]() |
814c504951 | ||
![]() |
5c2b96a812 | ||
![]() |
3dc1ca8adb | ||
![]() |
a6850f1bc0 | ||
![]() |
5ba6cb9135 | ||
![]() |
500283af6f | ||
![]() |
43184dccda | ||
![]() |
4296a3b5df | ||
![]() |
14ba50f061 | ||
![]() |
9ad7de56a3 | ||
![]() |
5570687957 | ||
![]() |
1406bf3e10 | ||
![]() |
287f299b94 | ||
![]() |
f88f05cd46 | ||
![]() |
c6fb42e2f3 | ||
![]() |
c64ca9d9b7 | ||
![]() |
7d47793afb | ||
![]() |
2840fce856 | ||
![]() |
2791dca412 | ||
![]() |
3e268bf66b | ||
![]() |
f44422a77e | ||
![]() |
4931cbcc34 | ||
![]() |
59e495f7d3 | ||
![]() |
93866d7bc2 | ||
![]() |
ba1cdeaeeb | ||
![]() |
234b27e555 | ||
![]() |
5acfd09819 | ||
![]() |
9923b15ecd | ||
![]() |
def5a6d9d0 | ||
![]() |
17c8a405f5 | ||
![]() |
ff874bfb48 | ||
![]() |
4cbb37b996 | ||
![]() |
f57e35c6b8 | ||
![]() |
312e786e33 | ||
![]() |
e2c75a2daf | ||
![]() |
fce20263ea | ||
![]() |
d841f9cb62 | ||
![]() |
4945240ec2 | ||
![]() |
f1cdc7e422 | ||
![]() |
372cdb10d6 | ||
![]() |
fd24dbee21 | ||
![]() |
7066f7ea76 | ||
![]() |
5ae53c79b6 | ||
![]() |
f3a8ab289f | ||
![]() |
a99609e3da | ||
![]() |
a271189448 | ||
![]() |
251bc28526 | ||
![]() |
05505704e4 | ||
![]() |
4aadcb021e | ||
![]() |
ae9da1b354 | ||
![]() |
a594029541 | ||
![]() |
ddcb894932 | ||
![]() |
708cdbe545 | ||
![]() |
1cbf96dff1 | ||
![]() |
bd55b37d5f | ||
![]() |
7c3a0effee | ||
![]() |
fe3048aab0 | ||
![]() |
9a5a3e879d | ||
![]() |
269902db94 | ||
![]() |
4ba67ea863 | ||
![]() |
3534712478 | ||
![]() |
62b9a8071a | ||
![]() |
cf83c27ca0 | ||
![]() |
8035f81f97 | ||
![]() |
d2375fdc54 | ||
![]() |
f07d9dd813 | ||
![]() |
b5524b18cf | ||
![]() |
310497a5bf | ||
![]() |
47a738d5e6 | ||
![]() |
6ad13c1da0 | ||
![]() |
99454fdc4b | ||
![]() |
799f0ead6c | ||
![]() |
d16d709b72 | ||
![]() |
8e1087b818 | ||
![]() |
139dcb521e | ||
![]() |
b17e431473 | ||
![]() |
b1ee3ef8ba | ||
![]() |
75bbd16b0c | ||
![]() |
71a7943d01 | ||
![]() |
d34785b5b0 | ||
![]() |
af58ef7244 | ||
![]() |
6d3bec8518 | ||
![]() |
0346b157c5 | ||
![]() |
e25aab742b | ||
![]() |
6f2ca00263 | ||
![]() |
e1d26325f3 | ||
![]() |
4202f963c3 | ||
![]() |
d3ef45db1b | ||
![]() |
dec55709a3 | ||
![]() |
b5ed984f05 | ||
![]() |
66d7baa126 | ||
![]() |
a2809a14c5 | ||
![]() |
040ad9edb0 | ||
![]() |
46dbe009f2 | ||
![]() |
3598d43938 | ||
![]() |
f1358c7ad1 | ||
![]() |
40862fcd01 | ||
![]() |
189432c228 | ||
![]() |
280d16f3d9 | ||
![]() |
722c39590f | ||
![]() |
b1138dbf05 | ||
![]() |
08918282a7 | ||
![]() |
5007b0bf1a | ||
![]() |
0a5912eb8e | ||
![]() |
8acd7b03ed | ||
![]() |
ed87df212f | ||
![]() |
56243aa076 | ||
![]() |
41e9f32e1b | ||
![]() |
f7753aa1b4 | ||
![]() |
aebfcc38dd | ||
![]() |
3e0149c058 | ||
![]() |
1e62e09825 | ||
![]() |
956b15a2eb | ||
![]() |
572b457b43 | ||
![]() |
7d40ae009f | ||
![]() |
aec9595dea | ||
![]() |
cc90f19a46 | ||
![]() |
ab486c8ed1 | ||
![]() |
bfb1e817ec | ||
![]() |
ebc7c22388 | ||
![]() |
10b4066c82 | ||
![]() |
42dd67954d | ||
![]() |
d45fdf605f | ||
![]() |
caef9bb8b5 | ||
![]() |
c888934601 | ||
![]() |
95613b595e | ||
![]() |
9247bd9d9e | ||
![]() |
ee3d2489e6 | ||
![]() |
7764c4fbcb | ||
![]() |
a18b524859 | ||
![]() |
9b8ec9b85e | ||
![]() |
7b3f070973 | ||
![]() |
ba27fc12e8 | ||
![]() |
db55912f78 | ||
![]() |
4c4bd267d4 | ||
![]() |
dc1002659b | ||
![]() |
97da370301 | ||
![]() |
571901f333 | ||
![]() |
4d90df9d9a | ||
![]() |
2c18667ffd | ||
![]() |
89157cd606 | ||
![]() |
37e0091ef0 | ||
![]() |
ae3512fecf | ||
![]() |
8b81391e2f | ||
![]() |
54e68f6252 | ||
![]() |
92d1ed65ff | ||
![]() |
8098a7ee5d | ||
![]() |
c24297630c | ||
![]() |
a9d5212602 | ||
![]() |
c81db8ae19 | ||
![]() |
ecd356d42b | ||
![]() |
e67adf87b2 | ||
![]() |
1669708041 | ||
![]() |
12e34013f8 | ||
![]() |
bd2ad1d7a1 | ||
![]() |
934cdb8237 | ||
![]() |
2f6ea8830e | ||
![]() |
b77d08ebbf | ||
![]() |
b735d32cbc | ||
![]() |
472ffd5b5c | ||
![]() |
4567ca8fce | ||
![]() |
07ed24ca7a | ||
![]() |
52575be2a7 | ||
![]() |
c8187e52bb | ||
![]() |
b0b6b72b4c | ||
![]() |
7676bc5836 | ||
![]() |
f98c1725be | ||
![]() |
c6bd599b63 | ||
![]() |
7908779c89 | ||
![]() |
29ad68afab | ||
![]() |
9aaeac6a08 | ||
![]() |
8f074c2131 | ||
![]() |
930653c86d | ||
![]() |
cc31b7c210 | ||
![]() |
bb19e9308c | ||
![]() |
5e7b4bfe45 | ||
![]() |
dbeeb61cc5 | ||
![]() |
26d8e5856a | ||
![]() |
66be6d1e89 | ||
![]() |
677aa232e7 | ||
![]() |
cabbb45031 | ||
![]() |
ba99df645b | ||
![]() |
282e5ba2d8 | ||
![]() |
42d418da58 | ||
![]() |
cbf270fdba | ||
![]() |
bb9abe104f | ||
![]() |
18f0d6dea3 | ||
![]() |
23dc9a1139 | ||
![]() |
5e18ef5830 | ||
![]() |
63f8fc266d | ||
![]() |
4e46b16f7b | ||
![]() |
ba19b50005 | ||
![]() |
fa867387d4 | ||
![]() |
5762cf5dc5 | ||
![]() |
7d9f624805 | ||
![]() |
5b335ccd59 | ||
![]() |
4792853eb6 | ||
![]() |
cc05bc7db8 | ||
![]() |
d198eaa988 | ||
![]() |
f094da6a4b | ||
![]() |
a53961b235 | ||
![]() |
f644113af8 | ||
![]() |
e4a903ec07 | ||
![]() |
9408fe2a07 | ||
![]() |
394e747a88 | ||
![]() |
e91f12729b | ||
![]() |
134e588b14 | ||
![]() |
d061eb7b58 | ||
![]() |
18089a8076 | ||
![]() |
02236e01d9 | ||
![]() |
c1150d50b1 | ||
![]() |
387a849269 | ||
![]() |
399cebda70 | ||
![]() |
72720b3dfe | ||
![]() |
64f7560b3b | ||
![]() |
e06b646f49 | ||
![]() |
b51d9bb17b | ||
![]() |
4e967c5720 | ||
![]() |
179db38fd1 | ||
![]() |
e51930d7e1 | ||
![]() |
74a299dbe6 | ||
![]() |
282863c526 | ||
![]() |
63d794ed3e | ||
![]() |
c6b8f12f9a | ||
![]() |
d16e292231 | ||
![]() |
29a319a850 | ||
![]() |
c11e0db077 | ||
![]() |
b2dafb5dfa | ||
![]() |
e7ccd01427 | ||
![]() |
447f26458a | ||
![]() |
61fb71a080 | ||
![]() |
329fcdf8f4 | ||
![]() |
37cdb34014 | ||
![]() |
5ee7b85cc4 | ||
![]() |
1f7228f95a | ||
![]() |
fa8418adcd | ||
![]() |
d3b1765ffe | ||
![]() |
0a274ebadb | ||
![]() |
0d1b35edc5 | ||
![]() |
d7e4ae53ce | ||
![]() |
61ceffc6f9 | ||
![]() |
0c422bfd21 | ||
![]() |
a0815b06a6 | ||
![]() |
bd02b7574a | ||
![]() |
1048d923d0 | ||
![]() |
9dbfcf4262 | ||
![]() |
8ea176b5f0 | ||
![]() |
90dcbadc52 | ||
![]() |
e4021bf830 | ||
![]() |
b8b453aba0 | ||
![]() |
aeec2377c1 | ||
![]() |
f8b0ffd39b | ||
![]() |
9953c3c823 | ||
![]() |
d227a07fe9 | ||
![]() |
7c394414d8 | ||
![]() |
1dfb22d02e | ||
![]() |
1847ad5622 | ||
![]() |
beb701ceb4 | ||
![]() |
2fbadea821 | ||
![]() |
bac561d8c7 | ||
![]() |
7bd261a02e | ||
![]() |
df99a889f0 | ||
![]() |
16c5892a1d | ||
![]() |
0236bbaf68 | ||
![]() |
2ca5a290c3 | ||
![]() |
66c388a644 | ||
![]() |
74a77ed271 | ||
![]() |
3d98e6cdc0 | ||
![]() |
831ae96e0f | ||
![]() |
113a91a73f | ||
![]() |
2be8e14f66 | ||
![]() |
9cfaa9e603 | ||
![]() |
48653c7590 | ||
![]() |
72450b6305 | ||
![]() |
7d22a1c105 | ||
![]() |
08ac3fb984 | ||
![]() |
2f64a074c6 | ||
![]() |
f98c797311 | ||
![]() |
d81fb14b04 | ||
![]() |
01bb1259eb | ||
![]() |
1118d025bf | ||
![]() |
146f0604c3 | ||
![]() |
f90afe8b37 | ||
![]() |
fff5306fa6 | ||
![]() |
078ea24cd2 | ||
![]() |
85a3558074 | ||
![]() |
73a9ae1c9c | ||
![]() |
2222ede420 | ||
![]() |
c6d80a9b0e | ||
![]() |
23c4414398 | ||
![]() |
8b9320bbf9 | ||
![]() |
a8ac288f17 | ||
![]() |
ffe7de6774 | ||
![]() |
d87a826493 | ||
![]() |
645fe29060 | ||
![]() |
4db738a7f6 | ||
![]() |
253c2c4317 | ||
![]() |
49755303f7 | ||
![]() |
1e137f0bd8 | ||
![]() |
c2579b1850 | ||
![]() |
577d5215cd | ||
![]() |
0870fa7e8f | ||
![]() |
ddb3795985 | ||
![]() |
45e98cd5b7 | ||
![]() |
4a99b7a3f2 | ||
![]() |
985132da78 | ||
![]() |
315edea5ca | ||
![]() |
27e0ef4e38 | ||
![]() |
8090d8be5a | ||
![]() |
8c8fd98bd5 | ||
![]() |
6f724b5bec | ||
![]() |
909cbd1f0c | ||
![]() |
094de0ca21 | ||
![]() |
af8ca4af9e | ||
![]() |
709e788f5f | ||
![]() |
1a4a1f7f8b | ||
![]() |
ff0bde059a | ||
![]() |
ac671110a3 | ||
![]() |
30c95da90e | ||
![]() |
0b52a1bd01 | ||
![]() |
3fc7fce9ca | ||
![]() |
f8df7987b8 | ||
![]() |
697594b63e | ||
![]() |
7be3923224 | ||
![]() |
5b913aa30a | ||
![]() |
da9cb7d6eb | ||
![]() |
aca19f7f73 | ||
![]() |
faf151cd62 | ||
![]() |
675cd4de2a | ||
![]() |
74ebe492c5 | ||
![]() |
254fcecf33 | ||
![]() |
a334864980 | ||
![]() |
b6b377c209 | ||
![]() |
5f76aa02dd | ||
![]() |
0ba0c998e9 | ||
![]() |
2e54216469 | ||
![]() |
56dbe62cad | ||
![]() |
048db60a10 | ||
![]() |
f3b1b2254f | ||
![]() |
e705aaa2f6 | ||
![]() |
1f80f843ba | ||
![]() |
02a76418fc | ||
![]() |
d1245705de | ||
![]() |
6d74f7c64d | ||
![]() |
bf1076b7d2 | ||
![]() |
7175d92eaf | ||
![]() |
f30ac46672 | ||
![]() |
0970db3295 | ||
![]() |
5d24216124 | ||
![]() |
9a55333ce8 | ||
![]() |
cb58ab5fb7 | ||
![]() |
5d9ed13003 | ||
![]() |
5b51cb30f5 | ||
![]() |
d8c5850eaa | ||
![]() |
4839b100cb | ||
![]() |
469050914b | ||
![]() |
8036d20bf9 | ||
![]() |
f04597335f | ||
![]() |
3d26ca6cc2 | ||
![]() |
ec3454291b | ||
![]() |
21e2f394a9 | ||
![]() |
dd9f69e4cd | ||
![]() |
f7e8cb24ee | ||
![]() |
3b5a77ec22 | ||
![]() |
16715129d1 | ||
![]() |
3aafaabda6 | ||
![]() |
279abfedfd | ||
![]() |
e77580dd35 | ||
![]() |
9cde662092 | ||
![]() |
862734c572 | ||
![]() |
61dc611588 | ||
![]() |
03368e2cca | ||
![]() |
6382325280 | ||
![]() |
a5b0910b60 | ||
![]() |
ba1e1ef20c | ||
![]() |
dd8bab299d | ||
![]() |
1125f31810 | ||
![]() |
ef3c93a907 | ||
![]() |
89bcaa96ed | ||
![]() |
ce1eecf82e | ||
![]() |
3164663b4f | ||
![]() |
4f99bc36d0 | ||
![]() |
179c0a39d4 | ||
![]() |
0fefa4e43a | ||
![]() |
d4e8eb276d | ||
![]() |
fd3c26ad73 | ||
![]() |
d9f9056515 | ||
![]() |
d326b5a36a | ||
![]() |
8c4970b550 | ||
![]() |
51ccdf3577 | ||
![]() |
f79245d24a | ||
![]() |
e8d8a5e89d | ||
![]() |
b1dddcb511 | ||
![]() |
af45b8a1e4 | ||
![]() |
2c8b60ab9b | ||
![]() |
75196cbf84 | ||
![]() |
5d80fd523c | ||
![]() |
22dc61f39b | ||
![]() |
f4611d88cd | ||
![]() |
346a706e41 | ||
![]() |
3c62f182ab | ||
![]() |
67964192de | ||
![]() |
c6f62fd078 | ||
![]() |
8ea547c1d7 | ||
![]() |
c5fedda195 | ||
![]() |
8300a4b6df | ||
![]() |
32e77de8bb | ||
![]() |
e1c080f237 | ||
![]() |
6995323a45 | ||
![]() |
46bfc597ec | ||
![]() |
0abc614a78 | ||
![]() |
7518456fe8 | ||
![]() |
4ab76e7507 | ||
![]() |
1cf5a88582 | ||
![]() |
14a8551e82 | ||
![]() |
4b0916a214 | ||
![]() |
e451dd2262 | ||
![]() |
2e4ecfab16 | ||
![]() |
8c343abac5 | ||
![]() |
39ac3ff4c2 | ||
![]() |
677880c633 | ||
![]() |
bd2f2cbf64 | ||
![]() |
4129264d1d | ||
![]() |
f9133a6e96 | ||
![]() |
57a23946cc | ||
![]() |
6faa45ac16 | ||
![]() |
bf62ebd20a | ||
![]() |
687c6a09bc | ||
![]() |
77a7df1cff | ||
![]() |
05ce3edb80 | ||
![]() |
2564d7d976 | ||
![]() |
2f06cda82b | ||
![]() |
945b777c3c | ||
![]() |
d2a6c45fd6 | ||
![]() |
b5436fe7fa | ||
![]() |
82e3b40e7c | ||
![]() |
0f4f36c654 | ||
![]() |
d83e8dabf8 | ||
![]() |
397926f994 | ||
![]() |
8d8a9f7c78 | ||
![]() |
40b8860e7c | ||
![]() |
9978241329 | ||
![]() |
666a1ab81e | ||
![]() |
1d6ed8d9d0 | ||
![]() |
34e2c77934 | ||
![]() |
f47d912074 | ||
![]() |
f25b67c81c | ||
![]() |
08e60e6961 | ||
![]() |
c393a80185 | ||
![]() |
8e28ec491c | ||
![]() |
2e391cc651 | ||
![]() |
2e0b2191c0 | ||
![]() |
4240849a2a | ||
![]() |
a0f0db1dd4 | ||
![]() |
e455497596 | ||
![]() |
bb99dd82ba | ||
![]() |
48ccddb555 | ||
![]() |
23f56e4deb | ||
![]() |
ef27c9348b | ||
![]() |
cb664b563d | ||
![]() |
53f38c583a | ||
![]() |
12622e2045 | ||
![]() |
36dfedbaab | ||
![]() |
d3a059d759 | ||
![]() |
9ba1351a3b | ||
![]() |
399f8ed1db | ||
![]() |
d36b5e8091 | ||
![]() |
fc616e818a | ||
![]() |
edd438c37f | ||
![]() |
92e5ae0ebd | ||
![]() |
d3e13c30a6 | ||
![]() |
6bf0e799a1 | ||
![]() |
166dda4a4b | ||
![]() |
901605fd9c | ||
![]() |
e3e8f570f8 | ||
![]() |
6fdfdefe5c | ||
![]() |
5cc1afa6de | ||
![]() |
4398570583 | ||
![]() |
2e5b354f49 | ||
![]() |
11f96e8039 | ||
![]() |
7ae1a079ea | ||
![]() |
b6d2624c4a | ||
![]() |
51d6362863 | ||
![]() |
e30bfe67ed | ||
![]() |
c5bcebad9a | ||
![]() |
5deead7f94 | ||
![]() |
2cff284122 | ||
![]() |
5c78e6e7bf | ||
![]() |
e8b06e9b78 | ||
![]() |
d67db9c63e | ||
![]() |
0dfdbc809c | ||
![]() |
adddf23ae3 | ||
![]() |
eac3fd24a1 | ||
![]() |
599c0f8cfc | ||
![]() |
b1ec8467a6 | ||
![]() |
d2b8d2beff | ||
![]() |
a415c7ef4e | ||
![]() |
1faf86a9cb | ||
![]() |
3b7d2e9238 | ||
![]() |
829d0cafe9 | ||
![]() |
a103ca9747 | ||
![]() |
81c41ef5ee | ||
![]() |
10e2794ff9 | ||
![]() |
6d3606abf0 | ||
![]() |
bbace2b2c1 | ||
![]() |
5aa937f56a | ||
![]() |
44159af83e | ||
![]() |
98041b7af2 | ||
![]() |
72e1fe3c21 | ||
![]() |
af8d1190c5 | ||
![]() |
b07e564a23 | ||
![]() |
4b8ac0ff9c | ||
![]() |
0d479426ed | ||
![]() |
e956b2d959 | ||
![]() |
580e97ffe3 | ||
![]() |
1cb951056a | ||
![]() |
a973887db2 | ||
![]() |
86ae81526a | ||
![]() |
9d62a552fe | ||
![]() |
5497084b57 | ||
![]() |
39b7effc26 | ||
![]() |
2c847f62af | ||
![]() |
01ac26de8c | ||
![]() |
c579eebd16 | ||
![]() |
47afd60ddf | ||
![]() |
f432e72253 | ||
![]() |
d572623552 | ||
![]() |
3a129557e0 | ||
![]() |
49fac8f147 | ||
![]() |
3140fb2435 | ||
![]() |
22cea145f8 | ||
![]() |
6c93d46ec1 | ||
![]() |
3c73064b19 | ||
![]() |
247ac1f96f | ||
![]() |
642f32ab4e | ||
![]() |
41cb1c71c7 | ||
![]() |
41de2aa769 | ||
![]() |
e9e7b679f4 | ||
![]() |
c9022f4208 | ||
![]() |
0a4877993d | ||
![]() |
ca74fa626a | ||
![]() |
608038cbcc | ||
![]() |
bf009ae13b | ||
![]() |
bdb7a1b840 | ||
![]() |
d2b0a57064 | ||
![]() |
dbceed563a | ||
![]() |
cbf1d86c26 | ||
![]() |
06a2014bc1 | ||
![]() |
ad46b3eea3 | ||
![]() |
5df7867b6d | ||
![]() |
ceea962464 | ||
![]() |
72386bc589 | ||
![]() |
34068a8188 | ||
![]() |
414d521588 | ||
![]() |
d15e3d4c36 | ||
![]() |
81b850c1f7 | ||
![]() |
527dfe6971 | ||
![]() |
d681d06de1 | ||
![]() |
9fe8ef0238 | ||
![]() |
99640b3d73 | ||
![]() |
c9925ba8fe | ||
![]() |
3b829af43d | ||
![]() |
2abe23f918 | ||
![]() |
475f24f661 | ||
![]() |
e338770e57 | ||
![]() |
6bb2a583de | ||
![]() |
74bbbf55f8 | ||
![]() |
20b87cb000 | ||
![]() |
a3d82c5d70 | ||
![]() |
c778c0c111 | ||
![]() |
bb7ef994f7 | ||
![]() |
278b1ed7c0 | ||
![]() |
08bfb953de | ||
![]() |
276b1ef4cd | ||
![]() |
cbd408ae89 | ||
![]() |
fc6822a298 | ||
![]() |
43b7e506a7 | ||
![]() |
f11a4b8472 | ||
![]() |
927ceb6768 | ||
![]() |
3d9249192c | ||
![]() |
243e50465f | ||
![]() |
d539bc6fc8 | ||
![]() |
6fc52c8b4c | ||
![]() |
df3bf4ce11 | ||
![]() |
c35753c63e | ||
![]() |
8d2cac5018 | ||
![]() |
a8c8efb4bd | ||
![]() |
c94b52aa61 | ||
![]() |
f1d319601e | ||
![]() |
00a0efc945 | ||
![]() |
f175ada782 | ||
![]() |
ede55a70aa | ||
![]() |
7d82ca5d3c | ||
![]() |
6a33636d9d | ||
![]() |
a03c60bd90 | ||
![]() |
4b122bd19e | ||
![]() |
c386b001ba | ||
![]() |
9d5e54c2d9 | ||
![]() |
2ffce75056 | ||
![]() |
ff76e37efc | ||
![]() |
ae3d547c3f | ||
![]() |
a52e365b88 | ||
![]() |
fa7b7d8781 | ||
![]() |
f057501611 | ||
![]() |
e740d4fe25 | ||
![]() |
dbcf8fde7f | ||
![]() |
b4a4f47f78 | ||
![]() |
362c8dc25d | ||
![]() |
18b0a41a40 | ||
![]() |
d43f77a894 | ||
![]() |
aeeb058ebb | ||
![]() |
e57122c4ff | ||
![]() |
802a9bd2e2 | ||
![]() |
b63603afb6 | ||
![]() |
d6c0690324 | ||
![]() |
1cfe84fc3d | ||
![]() |
bcfc9beb99 | ||
![]() |
cb839637ab | ||
![]() |
6dc795fe45 | ||
![]() |
b3f216209a | ||
![]() |
571bf6bfa7 | ||
![]() |
f272e5c4a8 | ||
![]() |
d8a91ac62d | ||
![]() |
92b0c5e409 | ||
![]() |
dfa076ba89 | ||
![]() |
79e609e682 | ||
![]() |
9c40bc5863 | ||
![]() |
7b18937530 | ||
![]() |
c2b020fc94 | ||
![]() |
798769d5ae | ||
![]() |
9546d76dc9 | ||
![]() |
6964f7ebec | ||
![]() |
e58b7166ad | ||
![]() |
d881cfeeb0 | ||
![]() |
854dc6f61a | ||
![]() |
1a3345e521 | ||
![]() |
8632b29719 | ||
![]() |
24a3c3fd1c | ||
![]() |
115a46d2e7 | ||
![]() |
cd5502fdde | ||
![]() |
53bbde4314 | ||
![]() |
9506c22016 | ||
![]() |
f3f9af9fdf | ||
![]() |
2f9e40fc0e | ||
![]() |
c46fbef03c | ||
![]() |
9a973c8257 | ||
![]() |
c2c8973dd5 | ||
![]() |
b178807a39 | ||
![]() |
179fe512af | ||
![]() |
964a38db2c | ||
![]() |
2b38e5a0dd | ||
![]() |
d8e1f6df81 | ||
![]() |
c3a54e0c69 | ||
![]() |
2b6ff7de41 | ||
![]() |
ae58e629ea | ||
![]() |
18ccb25b29 | ||
![]() |
74d7e472af | ||
![]() |
232ba230eb | ||
![]() |
031e175e9f | ||
![]() |
87fdc02224 | ||
![]() |
3308a44c2b | ||
![]() |
796121acfc | ||
![]() |
8e0553147b | ||
![]() |
96007a1f48 | ||
![]() |
5189e5b131 | ||
![]() |
11dc650e0e | ||
![]() |
baebec2270 | ||
![]() |
419d84da97 | ||
![]() |
7d7c96274d | ||
![]() |
4553084503 | ||
![]() |
30bd56b984 | ||
![]() |
4292517a86 | ||
![]() |
f8c7e601a5 | ||
![]() |
fbc867b4fa | ||
![]() |
1644bfeb43 | ||
![]() |
7cc82eb143 | ||
![]() |
5c13c81de9 | ||
![]() |
1b80db1eba | ||
![]() |
30d2740368 | ||
![]() |
8fb58cf662 | ||
![]() |
3b4ad0d9c7 | ||
![]() |
abe4ebe66a | ||
![]() |
ad1514c60b | ||
![]() |
81ff07ec28 | ||
![]() |
7cd45a5ca3 | ||
![]() |
e27a941425 | ||
![]() |
f2c9adf2ef | ||
![]() |
8dfb919aa0 | ||
![]() |
4cfae2d9c6 | ||
![]() |
6b8e44a952 | ||
![]() |
6b40d206f8 | ||
![]() |
524c3c22dc | ||
![]() |
d39cf2f051 | ||
![]() |
b2caea9b9b | ||
![]() |
ea76b5b762 | ||
![]() |
bda7ad9f2f | ||
![]() |
aa8d1d984c | ||
![]() |
4d54a608a6 | ||
![]() |
6466606f49 | ||
![]() |
ca52bee808 | ||
![]() |
8e3ef4873f | ||
![]() |
274d0eb2fd | ||
![]() |
61cf45697c | ||
![]() |
cb8f6f7b75 | ||
![]() |
3978e8cd0d | ||
![]() |
d816446c5e | ||
![]() |
70f1aaab15 | ||
![]() |
cccdb49075 | ||
![]() |
71d6a57cdc | ||
![]() |
05075fc1c4 | ||
![]() |
a1692c3c37 | ||
![]() |
d5cecde0e1 | ||
![]() |
b0dc0adf0e | ||
![]() |
2bb058da36 | ||
![]() |
a56135ca57 | ||
![]() |
bcff9f5a9e | ||
![]() |
59b9c66a91 | ||
![]() |
0f63f6d453 | ||
![]() |
585adb14e6 | ||
![]() |
00be50d2fd | ||
![]() |
7e2dd95134 | ||
![]() |
64807fbc11 | ||
![]() |
204a8ffb7f | ||
![]() |
aef097becb | ||
![]() |
9a743fb4a8 | ||
![]() |
525472d3e0 | ||
![]() |
d2e4f0d143 | ||
![]() |
120a4c2b07 | ||
![]() |
97f88d6c4a | ||
![]() |
1613ab503c | ||
![]() |
45bf274dc0 | ||
![]() |
858737c25c | ||
![]() |
ee31cfd390 | ||
![]() |
5dae0edcf3 | ||
![]() |
e4dd7e454a | ||
![]() |
486e817a40 | ||
![]() |
91ec19c7df | ||
![]() |
f3f4ea5b60 | ||
![]() |
859b3e2db8 | ||
![]() |
3d836b45bf | ||
![]() |
4670e2fe0c | ||
![]() |
3440b89b04 | ||
![]() |
1458003536 | ||
![]() |
eaf3c1ecfd | ||
![]() |
12e73d59d5 | ||
![]() |
51059b0f39 | ||
![]() |
0b8b5aeebd | ||
![]() |
cd795a55d3 | ||
![]() |
922f86f20c | ||
![]() |
b8b2e3e3d2 | ||
![]() |
74aed833bf | ||
![]() |
04a5378e1b | ||
![]() |
dc0f374110 | ||
![]() |
7422f63c62 | ||
![]() |
3004cf1115 | ||
![]() |
a5e114ac68 | ||
![]() |
bc402883b3 | ||
![]() |
72599c6683 | ||
![]() |
1760c6e454 | ||
![]() |
3e05d7b5e9 | ||
![]() |
e9f7dcf030 | ||
![]() |
314787f39c | ||
![]() |
a69cd51dda | ||
![]() |
d1932a107b | ||
![]() |
670ebe5d37 | ||
![]() |
b92e0b9660 | ||
![]() |
664cd1d0cd | ||
![]() |
efc4588a00 | ||
![]() |
92a2d68a81 | ||
![]() |
ee9d095454 | ||
![]() |
b9450e3c55 | ||
![]() |
fe40e16e33 | ||
![]() |
8f5901753e | ||
![]() |
9005256cd3 | ||
![]() |
ace2436e43 | ||
![]() |
d83db08b81 | ||
![]() |
fd0d4dceca | ||
![]() |
6f0a763589 | ||
![]() |
0849801ae9 | ||
![]() |
c0dfc101b2 | ||
![]() |
b5e5ae7321 | ||
![]() |
39792d25d8 | ||
![]() |
20c0451c86 | ||
![]() |
8a883765d4 | ||
![]() |
cad42eed54 | ||
![]() |
a7f68f80d1 | ||
![]() |
c422ca530d | ||
![]() |
237ab48d33 | ||
![]() |
628f872180 | ||
![]() |
56a9aeece7 | ||
![]() |
3c3bb82e97 | ||
![]() |
d9192f6e6b | ||
![]() |
2e31e821b3 | ||
![]() |
6895378d33 | ||
![]() |
f2f99fda31 | ||
![]() |
56b7763488 | ||
![]() |
12bbcd7887 | ||
![]() |
b42c27b02f | ||
![]() |
e34ac3c485 | ||
![]() |
54bbc0d79c | ||
![]() |
3a186ebb1a | ||
![]() |
119650048e | ||
![]() |
ec8e57972a | ||
![]() |
0d04e1534d | ||
![]() |
7b3352ba03 | ||
![]() |
0cfb51dfb1 | ||
![]() |
84558af121 | ||
![]() |
48fd4a0dbe | ||
![]() |
21c16d7272 | ||
![]() |
f604e28ba4 | ||
![]() |
332a4a31f4 | ||
![]() |
8869802d4f | ||
![]() |
b0034de0a5 | ||
![]() |
c76491a8b0 | ||
![]() |
ff2edeea17 | ||
![]() |
284738ae7d | ||
![]() |
b1459cbf3b | ||
![]() |
5838216b72 | ||
![]() |
ce40f7d474 | ||
![]() |
f0d3db6f9a | ||
![]() |
e47c54b336 | ||
![]() |
38bcf6ada0 | ||
![]() |
8d92ba6083 | ||
![]() |
c0aa2e4f6c | ||
![]() |
3705391ecc | ||
![]() |
a2602cd2c8 | ||
![]() |
f5f8632678 | ||
![]() |
a36b805b7c | ||
![]() |
43cd8859bc | ||
![]() |
37548e6c39 | ||
![]() |
47930eca10 | ||
![]() |
1b405728c4 | ||
![]() |
5d7bf38d13 | ||
![]() |
2c2c1e8d89 | ||
![]() |
7f7ec6c115 | ||
![]() |
c9061b24a0 | ||
![]() |
a3dc7d2cde | ||
![]() |
cf90e7cfd6 | ||
![]() |
bbc9ea9571 | ||
![]() |
577abde2e9 | ||
![]() |
8e972fd875 | ||
![]() |
c7b290a380 | ||
![]() |
3d14208175 | ||
![]() |
04ad03cfd9 | ||
![]() |
48e9053124 | ||
![]() |
bd5aae3469 | ||
![]() |
2d3e307189 | ||
![]() |
c79c82cac7 | ||
![]() |
a80cd106bf | ||
![]() |
e1d953c855 | ||
![]() |
096837bf22 | ||
![]() |
ac4be28a03 | ||
![]() |
42ffd907ae | ||
![]() |
11ef2ac4bc | ||
![]() |
2862953136 | ||
![]() |
c958abdfcf | ||
![]() |
050f392dd1 | ||
![]() |
80b25694b5 | ||
![]() |
e462d1f7ea | ||
![]() |
e7b47f5c98 | ||
![]() |
89d7359060 | ||
![]() |
8a09408cf5 | ||
![]() |
45c0995d9d | ||
![]() |
0f3d1f0173 | ||
![]() |
a83983fe06 | ||
![]() |
b3937af636 | ||
![]() |
5037e67857 | ||
![]() |
ae76f7100c | ||
![]() |
0825eb36e4 | ||
![]() |
a58a59788d | ||
![]() |
77624acc01 | ||
![]() |
63ffd1f720 | ||
![]() |
8308265a62 | ||
![]() |
dc0e03245f | ||
![]() |
67cb57e7ac | ||
![]() |
4d5be952ce | ||
![]() |
0abab06124 | ||
![]() |
048689d28f | ||
![]() |
0be4b74d0e | ||
![]() |
26ed5cc794 | ||
![]() |
d236494c0b | ||
![]() |
52e4e09e85 | ||
![]() |
56c8666f02 | ||
![]() |
1404650566 | ||
![]() |
6a438a34f0 | ||
![]() |
816d63676d | ||
![]() |
8be352765e | ||
![]() |
038b38879f | ||
![]() |
3b8a12810c | ||
![]() |
e5bd4228b2 | ||
![]() |
8e60f3dfd7 | ||
![]() |
18f315d26c | ||
![]() |
fca140e3c9 | ||
![]() |
330902ccb3 | ||
![]() |
932994622e | ||
![]() |
37a60e3105 | ||
![]() |
bce11819ff | ||
![]() |
d97fab28e3 | ||
![]() |
620629ff8b | ||
![]() |
e149d47135 | ||
![]() |
45cce9956b | ||
![]() |
dd003845af | ||
![]() |
93b31c10ae | ||
![]() |
c9056516a1 | ||
![]() |
953a031ba6 | ||
![]() |
e2f04441c5 | ||
![]() |
8826125845 | ||
![]() |
d0bcf997fb | ||
![]() |
9429031a61 | ||
![]() |
556cdc17bd | ||
![]() |
db052baea3 | ||
![]() |
e858fc2df8 | ||
![]() |
d5d239937a | ||
![]() |
68fb34491c | ||
![]() |
6ab7265a04 | ||
![]() |
c13248fb44 | ||
![]() |
2bc5da885e | ||
![]() |
1bd7f7a1c5 | ||
![]() |
5e8884c3f4 | ||
![]() |
ed0d22dcd7 | ||
![]() |
7e960024dd | ||
![]() |
90f4c873bb | ||
![]() |
1e34ab1ec6 | ||
![]() |
4cf25a8781 | ||
![]() |
53264e0637 | ||
![]() |
7cb3c00709 | ||
![]() |
ef1619fafb | ||
![]() |
10b9d69ac8 | ||
![]() |
2223cd7491 | ||
![]() |
927a0983ee | ||
![]() |
bb752e2bce | ||
![]() |
121b99a526 | ||
![]() |
7d325f06f6 | ||
![]() |
a2d83f5f21 | ||
![]() |
85473f6dcf | ||
![]() |
bfb685047e | ||
![]() |
416649c8cd | ||
![]() |
aef77dde59 | ||
![]() |
e1354c15fc | ||
![]() |
b41fca7e6c | ||
![]() |
dc89da2420 | ||
![]() |
1a1e7e57fb | ||
![]() |
cc0c73f226 | ||
![]() |
3c46f747b5 | ||
![]() |
0f8e4613da | ||
![]() |
801052adcd | ||
![]() |
70af9981e7 | ||
![]() |
715c900d88 | ||
![]() |
8162ab4299 | ||
![]() |
a2eae0da6d | ||
![]() |
832876d711 | ||
![]() |
4d7ee3dcca | ||
![]() |
6a8c642b72 | ||
![]() |
fe200fb790 | ||
![]() |
601582002a | ||
![]() |
81349bfdb3 | ||
![]() |
ab71dd2974 | ||
![]() |
40dfbae70c | ||
![]() |
31613225af | ||
![]() |
09b3f96189 | ||
![]() |
cfef769604 | ||
![]() |
364e69600c | ||
![]() |
b57f0735c9 | ||
![]() |
58565fddae | ||
![]() |
4183884537 | ||
![]() |
8c12d2dc85 | ||
![]() |
b8970fc3f8 | ||
![]() |
dc8a32b0b4 | ||
![]() |
02deed3e3a | ||
![]() |
e50f641533 | ||
![]() |
bd754da1ed | ||
![]() |
23c568c87c | ||
![]() |
cc9b047300 | ||
![]() |
eb3ac23c01 | ||
![]() |
5358d6ce5d | ||
![]() |
c0eaab3254 | ||
![]() |
2a39435413 | ||
![]() |
01ea316c1f | ||
![]() |
b253040d0a | ||
![]() |
57748a3541 | ||
![]() |
486eb088cf | ||
![]() |
c07a741970 | ||
![]() |
d1c7d5ef70 | ||
![]() |
4b15dad5ea | ||
![]() |
577e3fc669 | ||
![]() |
b5c7c6d88b | ||
![]() |
d812bb431f | ||
![]() |
3593727d29 | ||
![]() |
75b536959e | ||
![]() |
29a9338ad7 | ||
![]() |
3755a3ce4c | ||
![]() |
ec0d0203ae | ||
![]() |
72e3a69bd9 | ||
![]() |
2c61fb0c8b | ||
![]() |
56352560a5 | ||
![]() |
2ac7c17514 | ||
![]() |
a5b3a68588 | ||
![]() |
10dcccf99f | ||
![]() |
d0922d85b3 | ||
![]() |
fc330e25cf | ||
![]() |
dc79d623be | ||
![]() |
91f5f1d628 | ||
![]() |
e0f055a375 | ||
![]() |
7f3098362a | ||
![]() |
26eee1bb63 | ||
![]() |
0246d48584 | ||
![]() |
92053ea25a | ||
![]() |
d12984f324 | ||
![]() |
0eb28ec1a5 | ||
![]() |
20f8cca07d | ||
![]() |
9613e142c9 | ||
![]() |
1cc752b5c7 | ||
![]() |
163629b990 | ||
![]() |
770e115be9 | ||
![]() |
0eb5f3d3e6 | ||
![]() |
6e9a9992c0 | ||
![]() |
827fef8a05 | ||
![]() |
b53fcdebe3 | ||
![]() |
6537a1c649 | ||
![]() |
abaad7cb82 | ||
![]() |
ba140f05d3 | ||
![]() |
81a444e056 | ||
![]() |
db2c3556de | ||
![]() |
9bd1447bcf | ||
![]() |
fda957b1f6 | ||
![]() |
29abf702bd | ||
![]() |
5ddb5ab6fa | ||
![]() |
e997aa6c81 | ||
![]() |
6271cedc25 | ||
![]() |
743b6d6587 | ||
![]() |
ee4303a676 | ||
![]() |
c361b9af8d | ||
![]() |
f66656fd4e | ||
![]() |
be141e55a9 | ||
![]() |
5ed3b9230e | ||
![]() |
44e3de8534 | ||
![]() |
cd6c5216ff | ||
![]() |
17010f9283 | ||
![]() |
5fb988ae2d | ||
![]() |
c4a3f19bba | ||
![]() |
347c9c9455 | ||
![]() |
731443ab7d | ||
![]() |
898ab41167 | ||
![]() |
cf4f5bd084 | ||
![]() |
c7e55fe3e0 | ||
![]() |
b737cf68ba | ||
![]() |
c95622affe | ||
![]() |
c1b637b284 | ||
![]() |
bb37299c5b | ||
![]() |
2e9d5fb2fc | ||
![]() |
2aeabd4be3 | ||
![]() |
a4a9d60d68 | ||
![]() |
dac9276b9e | ||
![]() |
38700256b0 | ||
![]() |
a5f391d2dc | ||
![]() |
77d7260698 | ||
![]() |
782b2bafaa | ||
![]() |
a35bee0bc9 | ||
![]() |
c7d5584cd9 | ||
![]() |
bb68b2dea1 | ||
![]() |
3b587de138 | ||
![]() |
c827ce4270 | ||
![]() |
c6c3cbb1d3 | ||
![]() |
ad97fae883 | ||
![]() |
a5b6e66e22 | ||
![]() |
5d7daa8886 | ||
![]() |
47ba42f9f8 | ||
![]() |
04f8a71244 | ||
![]() |
3148118784 | ||
![]() |
b7c4a63d2b | ||
![]() |
a4abd7e1cd | ||
![]() |
0b01a6386d | ||
![]() |
fa75f11eaf | ||
![]() |
a1b360a172 | ||
![]() |
e0a76ea918 | ||
![]() |
7990c68d65 | ||
![]() |
8c5d95796f | ||
![]() |
b901a396bf | ||
![]() |
fa92375e33 | ||
![]() |
04b3d93d5d | ||
![]() |
1f8bc9cfbd | ||
![]() |
71939f9163 | ||
![]() |
0ea8f3a01a | ||
![]() |
ffa49149c9 | ||
![]() |
40a35691fe | ||
![]() |
908a3126f1 | ||
![]() |
726707afe8 | ||
![]() |
3c926adeca | ||
![]() |
fc681d9ebc | ||
![]() |
364f53142c | ||
![]() |
2764aa2c06 | ||
![]() |
ca141b1076 | ||
![]() |
2e980664ac | ||
![]() |
03cfa8b904 | ||
![]() |
c5bb07a768 | ||
![]() |
3a1e8b4bbd | ||
![]() |
0e749936a6 | ||
![]() |
744b31aad6 | ||
![]() |
82bdf9d3b1 | ||
![]() |
8c59bd664e | ||
![]() |
15aaada3fe | ||
![]() |
0fdbe4d39b | ||
![]() |
280c7832ae | ||
![]() |
1f9dd6a3bc | ||
![]() |
e7f12f4a06 | ||
![]() |
5a24b9ec8a | ||
![]() |
1d8a72e03b | ||
![]() |
ef987eae36 | ||
![]() |
dc4899c240 | ||
![]() |
88a780f008 | ||
![]() |
6fc13f3707 | ||
![]() |
6f1b9b8fe7 | ||
![]() |
533d73d718 | ||
![]() |
63241d2438 | ||
![]() |
5a177b330a | ||
![]() |
ae8f701e5c | ||
![]() |
744c927b69 | ||
![]() |
6ca1b99947 | ||
![]() |
cdb018dcd2 | ||
![]() |
03efe3f0b3 | ||
![]() |
913a2773c1 | ||
![]() |
10c64167d7 | ||
![]() |
a624a8d8b5 | ||
![]() |
ff09a836b4 | ||
![]() |
e7c734c55e | ||
![]() |
5271af8b94 | ||
![]() |
df55d9fdd9 | ||
![]() |
c7ff9dc162 | ||
![]() |
f7ab2b667c | ||
![]() |
b89086d3b8 | ||
![]() |
448a1a49b4 | ||
![]() |
98d7969a1e | ||
![]() |
d513e03138 | ||
![]() |
a5367c3770 | ||
![]() |
58d5847eed | ||
![]() |
02b22740b2 | ||
![]() |
70d59c6c64 | ||
![]() |
095948e737 | ||
![]() |
e6cec355cc | ||
![]() |
351f152664 | ||
![]() |
04450b8793 | ||
![]() |
76612e81b2 | ||
![]() |
cb26948db6 | ||
![]() |
18cdd226bb | ||
![]() |
2199970e50 | ||
![]() |
8389c9fdad | ||
![]() |
0d06ec9a22 | ||
![]() |
ce56f166cc | ||
![]() |
a07938d517 | ||
![]() |
7d81a4bdd2 | ||
![]() |
c4bffe7f9d | ||
![]() |
0c2caccc7c | ||
![]() |
d3c9f7a491 | ||
![]() |
f09fa0fe7c | ||
![]() |
8f1fbf086f | ||
![]() |
272c666d23 | ||
![]() |
a1db89700b | ||
![]() |
311f0a747d | ||
![]() |
69298857a9 | ||
![]() |
c98680fa59 | ||
![]() |
5f357afcd6 | ||
![]() |
997775e54b | ||
![]() |
2cf79e27de | ||
![]() |
95d03e00da | ||
![]() |
c85aadf006 | ||
![]() |
e9ffb7ef82 | ||
![]() |
4237972c86 | ||
![]() |
b69009f8b6 | ||
![]() |
6fa6ee7a1b | ||
![]() |
7cf1bfffbc | ||
![]() |
903b0f52b9 | ||
![]() |
16e299a12d | ||
![]() |
e950d73742 | ||
![]() |
b44cccb972 | ||
![]() |
5d4ef1bbe8 | ||
![]() |
12a6912d97 | ||
![]() |
2740f8e23d | ||
![]() |
d0814477eb | ||
![]() |
cdb256390c | ||
![]() |
9895cc1488 | ||
![]() |
fad2495941 | ||
![]() |
f9fa7c4094 | ||
![]() |
a5538a07f1 | ||
![]() |
d1b46df78a | ||
![]() |
57186bf85d |
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-22.04
|
44
.devcontainer/boot.sh
Normal file
44
.devcontainer/boot.sh
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
CURRENT_DIR="$(pwd)"
|
||||||
|
BACKEND_PORT=3000
|
||||||
|
WEB_PORT=3001
|
||||||
|
|
||||||
|
echo "Configuring backend environment variables..."
|
||||||
|
cd packages/backend
|
||||||
|
rm -rf .env
|
||||||
|
echo "
|
||||||
|
PORT=$BACKEND_PORT
|
||||||
|
WEB_APP_URL=http://localhost:$WEB_PORT
|
||||||
|
APP_ENV=development
|
||||||
|
POSTGRES_DATABASE=automatisch
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_USERNAME=automatisch_user
|
||||||
|
POSTGRES_PASSWORD=automatisch_password
|
||||||
|
ENCRYPTION_KEY=sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY=sample_webhook_secret_key
|
||||||
|
APP_SECRET_KEY=sample_app_secret_key
|
||||||
|
REDIS_HOST=redis
|
||||||
|
SERVE_WEB_APP_SEPARATELY=true" >> .env
|
||||||
|
cd $CURRENT_DIR
|
||||||
|
|
||||||
|
echo "Configuring web environment variables..."
|
||||||
|
cd packages/web
|
||||||
|
rm -rf .env
|
||||||
|
echo "
|
||||||
|
PORT=$WEB_PORT
|
||||||
|
REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT
|
||||||
|
" >> .env
|
||||||
|
cd $CURRENT_DIR
|
||||||
|
|
||||||
|
echo "Installing and linking dependencies..."
|
||||||
|
yarn
|
||||||
|
yarn lerna bootstrap
|
||||||
|
|
||||||
|
echo "Migrating database..."
|
||||||
|
cd packages/backend
|
||||||
|
yarn db:migrate
|
||||||
|
yarn db:seed:user
|
||||||
|
|
||||||
|
echo "Done!"
|
53
.devcontainer/devcontainer.json
Normal file
53
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "Automatisch",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspace",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/git:1": {
|
||||||
|
"version": "latest"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
"version": 18
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/common-utils:1": {
|
||||||
|
"username": "vscode",
|
||||||
|
"uid": 1000,
|
||||||
|
"gid": 1000,
|
||||||
|
"installZsh": true,
|
||||||
|
"installOhMyZsh": true,
|
||||||
|
"configureZshAsDefaultShell": true,
|
||||||
|
"upgradePackages": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"hostRequirements": {
|
||||||
|
"cpus": 4,
|
||||||
|
"memory": "8gb",
|
||||||
|
"storage": "32gb"
|
||||||
|
},
|
||||||
|
|
||||||
|
"portsAttributes": {
|
||||||
|
"3000": {
|
||||||
|
"label": "Backend",
|
||||||
|
"onAutoForward": "silent",
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"3001": {
|
||||||
|
"label": "Frontend",
|
||||||
|
"onAutoForward": "silent",
|
||||||
|
"protocol": "http"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"forwardPorts": [3000, 3001],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
"postCreateCommand": ["bash", ".devcontainer/boot.sh"]
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
63
.devcontainer/docker-compose.yml
Normal file
63
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: .devcontainer/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- ..:/workspace:cached
|
||||||
|
command: sleep infinity
|
||||||
|
postgres:
|
||||||
|
image: 'postgres:14.5-alpine'
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=automatisch
|
||||||
|
- POSTGRES_USER=automatisch_user
|
||||||
|
- POSTGRES_PASSWORD=automatisch_password
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
expose:
|
||||||
|
- 5432
|
||||||
|
redis:
|
||||||
|
image: 'redis:7.0.4-alpine'
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
expose:
|
||||||
|
- 6379
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:21.1
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- KEYCLOAK_ADMIN=admin
|
||||||
|
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||||
|
- KC_DB=postgres
|
||||||
|
- KC_DB_URL_HOST=postgres
|
||||||
|
- KC_DB_URL_DATABASE=keycloak
|
||||||
|
- KC_DB_USERNAME=automatisch_user
|
||||||
|
- KC_DB_PASSWORD=automatisch_password
|
||||||
|
- KC_HEALTH_ENABLED=true
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command: start-dev
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
healthcheck:
|
||||||
|
test: "curl -f http://localhost:8080/health/ready || exit 1"
|
||||||
|
volumes:
|
||||||
|
- keycloak:/opt/keycloak/data/
|
||||||
|
expose:
|
||||||
|
- 8080
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
keycloak:
|
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
**/node_modules/
|
||||||
|
**/dist/
|
||||||
|
**/logs/
|
||||||
|
**/.devcontainer
|
||||||
|
**/.github
|
||||||
|
**/.vscode
|
||||||
|
**/.env
|
||||||
|
**/.env.test
|
||||||
|
**/.env.production
|
||||||
|
**/yarn-error.log
|
||||||
|
packages/docs
|
||||||
|
packages/e2e-test
|
10
.eslintrc.js
10
.eslintrc.js
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
plugins: ['@typescript-eslint'],
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'prettier',
|
|
||||||
],
|
|
||||||
};
|
|
48
.github/workflows/backend.yml
vendored
Normal file
48
.github/workflows/backend.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Automatisch Backend Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14.5-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: automatisch_test
|
||||||
|
POSTGRES_USER: automatisch_test_user
|
||||||
|
POSTGRES_PASSWORD: automatisch_test_user_password
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U automatisch_test_user -d automatisch_test"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
redis:
|
||||||
|
image: redis:7.0.4-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd packages/backend && yarn
|
||||||
|
- name: Copy .env-example.test file to .env.test
|
||||||
|
run: cd packages/backend && cp .env-example.test .env.test
|
||||||
|
- name: Run tests
|
||||||
|
run: cd packages/backend && yarn test
|
61
.github/workflows/ci.yml
vendored
61
.github/workflows/ci.yml
vendored
@@ -1,5 +1,11 @@
|
|||||||
name: Automatisch CI
|
name: Automatisch CI
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linter:
|
linter:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -10,15 +16,15 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: yarn.lock
|
cache-dependency-path: yarn.lock
|
||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- run: yarn lint
|
- run: cd packages/backend && yarn lint
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
build-backend:
|
start-backend-server:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
||||||
@@ -27,13 +33,36 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: yarn.lock
|
cache-dependency-path: yarn.lock
|
||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
- run: yarn --frozen-lockfile && yarn lerna bootstrap
|
- run: yarn --frozen-lockfile && yarn lerna bootstrap
|
||||||
- run: cd packages/backend && yarn build
|
- run: cd packages/backend && yarn start
|
||||||
|
env:
|
||||||
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
|
start-backend-worker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
||||||
|
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
||||||
|
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: yarn.lock
|
||||||
|
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
|
- run: yarn --frozen-lockfile && yarn lerna bootstrap
|
||||||
|
- run: cd packages/backend && yarn start:worker
|
||||||
|
env:
|
||||||
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
build-web:
|
build-web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -44,7 +73,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '18'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: yarn.lock
|
cache-dependency-path: yarn.lock
|
||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
||||||
@@ -54,21 +83,3 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: false
|
CI: false
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
build-cli:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
|
|
||||||
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
|
|
||||||
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: yarn.lock
|
|
||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
|
||||||
- run: yarn --frozen-lockfile && yarn lerna bootstrap
|
|
||||||
- run: cd packages/backend && yarn build
|
|
||||||
- run: cd packages/cli && yarn build
|
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
|
||||||
|
32
.github/workflows/docs-change.yml
vendored
Normal file
32
.github/workflows/docs-change.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Automatisch Docs Change
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'packages/docs/**'
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Label PR
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { pull_request } = context.payload;
|
||||||
|
|
||||||
|
const label = 'documentation-change';
|
||||||
|
const hasLabel = pull_request.labels.some(({ name }) => name === label);
|
||||||
|
|
||||||
|
if (!hasLabel) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pull_request.number,
|
||||||
|
labels: [label],
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Label "${label}" added to PR #${pull_request.number}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Label "${label}" already exists on PR #${pull_request.number}`);
|
||||||
|
}
|
122
.github/workflows/playwright.yml
vendored
Normal file
122
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
name: Automatisch UI Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'packages/backend/**'
|
||||||
|
- 'packages/e2e-tests/**'
|
||||||
|
- 'packages/web/**'
|
||||||
|
- '!packages/backend/src/apps/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
|
APP_SECRET_KEY: sample_app_secret_key
|
||||||
|
POSTGRES_HOST: localhost
|
||||||
|
POSTGRES_DATABASE: automatisch
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_USERNAME: automatisch_user
|
||||||
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
APP_ENV: production
|
||||||
|
LICENSE_KEY: dummy_license_key
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14.5-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: automatisch
|
||||||
|
POSTGRES_USER: automatisch_user
|
||||||
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U automatisch_user -d automatisch"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
redis:
|
||||||
|
image: redis:7.0.4-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn && yarn lerna bootstrap
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: yarn playwright install --with-deps
|
||||||
|
- name: Build Automatisch web
|
||||||
|
working-directory: ./packages/web
|
||||||
|
run: yarn build
|
||||||
|
env:
|
||||||
|
# Keep this until we clean up warnings in build processes
|
||||||
|
CI: false
|
||||||
|
- name: Migrate database
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
run: yarn db:migrate
|
||||||
|
- name: Seed user
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
run: yarn db:seed:user &
|
||||||
|
- name: Install certutils
|
||||||
|
run: sudo apt install -y libnss3-tools
|
||||||
|
- name: Install mkcert
|
||||||
|
run: |
|
||||||
|
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" \
|
||||||
|
&& chmod +x mkcert-v*-linux-amd64 \
|
||||||
|
&& sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
|
||||||
|
- name: Install root certificate via mkcert
|
||||||
|
run: mkcert -install
|
||||||
|
- name: Create certificate
|
||||||
|
run: mkcert automatisch.io "*.automatisch.io" localhost 127.0.0.1 ::1
|
||||||
|
working-directory: ./packages/e2e-tests
|
||||||
|
- name: Set CAROOT environment variable
|
||||||
|
run: echo "NODE_EXTRA_CA_CERTS=$(mkcert -CAROOT)/rootCA.pem" >> "$GITHUB_ENV"
|
||||||
|
- name: Override license server with local server
|
||||||
|
run: sudo echo "127.0.0.1 license.automatisch.io" | sudo tee -a /etc/hosts
|
||||||
|
- name: Run local license server
|
||||||
|
working-directory: ./packages/e2e-tests
|
||||||
|
run: sudo yarn start-mock-license-server &
|
||||||
|
- name: Run Automatisch
|
||||||
|
run: yarn start &
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
- name: Run Automatisch worker
|
||||||
|
run: yarn start:worker &
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
- name: Setup upterm session
|
||||||
|
if: false
|
||||||
|
uses: lhotari/action-upterm@v1
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
limit-access-to-users: barinali
|
||||||
|
- name: Run Playwright tests
|
||||||
|
working-directory: ./packages/e2e-tests
|
||||||
|
env:
|
||||||
|
LOGIN_EMAIL: user@automatisch.io
|
||||||
|
LOGIN_PASSWORD: sample
|
||||||
|
BASE_URL: http://localhost:3000
|
||||||
|
GITHUB_CLIENT_ID: 1c0417daf898adfbd99a
|
||||||
|
GITHUB_CLIENT_SECRET: 3328fa814dd582ccd03dbe785cfd683fb8da92b3
|
||||||
|
run: yarn test
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: packages/e2e-tests/test-results
|
||||||
|
retention-days: 30
|
12
.gitignore
vendored
12
.gitignore
vendored
@@ -74,6 +74,15 @@ web_modules/
|
|||||||
.env.test
|
.env.test
|
||||||
.env.production
|
.env.production
|
||||||
|
|
||||||
|
# cypress environment variables file
|
||||||
|
cypress.env.json
|
||||||
|
|
||||||
|
# cypress screenshots
|
||||||
|
packages/e2e-tests/cypress/screenshots
|
||||||
|
|
||||||
|
# cypress videos
|
||||||
|
packages/e2e-tests/cypress/videos/
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
.cache
|
.cache
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
@@ -116,3 +125,6 @@ dist
|
|||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
# MacOS finder preferences
|
||||||
|
.DS_store
|
||||||
|
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
18.19.0
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our community include:
|
||||||
|
|
||||||
|
Demonstrating empathy and kindness toward other people
|
||||||
|
Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
Giving and gracefully accepting constructive feedback
|
||||||
|
Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||||
|
Focusing on what is best not just for us as individuals, but for the overall community
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||||
|
Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
Public or private harassment
|
||||||
|
Publishing others’ private information, such as a physical or email address, without their explicit permission
|
||||||
|
Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at ali@automatisch.io. All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
Community Impact: A violation through a single incident or series of actions.
|
||||||
|
|
||||||
|
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
|
||||||
|
|
||||||
|
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
Consequence: A permanent ban from any sort of public interaction within the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
5
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
5
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Automatisch Contributor License Agreement
|
||||||
|
|
||||||
|
I give Automatisch permission to license my contributions on any terms they like. I am giving them this license in order to make it possible for them to accept my contributions into their project.
|
||||||
|
|
||||||
|
**_As far as the law allows, my contributions come as is, without any warranty or condition, and I will not be liable to anyone for any damages related to this software or this license, under any kind of legal claim._**
|
3
LICENSE
Normal file
3
LICENSE
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
LICENSE.agpl (AGPL-3.0) applies to all files in this
|
||||||
|
repository, except for files that contain ".ee." in their name
|
||||||
|
which are covered by LICENSE.enterprise.
|
35
LICENSE.enterprise
Normal file
35
LICENSE.enterprise
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
The Automatisch Enterprise license (the “Enterprise License”)
|
||||||
|
Copyright (c) 2023-present AB Software GmbH.
|
||||||
|
|
||||||
|
With regard to the Automatisch Software:
|
||||||
|
|
||||||
|
This software and associated documentation files (the "Software") may only be
|
||||||
|
used in production, if you (and any entity that you represent) have a valid
|
||||||
|
Automatisch Enterprise license for the correct number of user seats. Subject
|
||||||
|
to the foregoing sentence, you are free to modify this Software and publish
|
||||||
|
patches to the Software. You agree that Automatisch and/or its licensors
|
||||||
|
(as applicable) retain all right, title and interest in and to all such
|
||||||
|
modifications and/or patches, and all such modifications and/or patches may
|
||||||
|
only be used, copied, modified, displayed, distributed, or otherwise exploited
|
||||||
|
with a valid Automatisch Enterprise license for the correct number of user seats.
|
||||||
|
Notwithstanding the foregoing, you may copy and modify the Software for
|
||||||
|
development and testing purposes, without requiring a subscription. You agree
|
||||||
|
that Automatisch and/or its licensors (as applicable) retain all right, title
|
||||||
|
and interest in and to all such modifications. You are not granted any other
|
||||||
|
rights beyond what is expressly stated herein. Subject to the foregoing, it is
|
||||||
|
forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
|
||||||
|
|
||||||
|
The full text of this Enterprise License shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
For all third party components incorporated into the Automatisch Software, those
|
||||||
|
components are licensed under the original license provided by the owner of the
|
||||||
|
applicable component.
|
61
README.md
Normal file
61
README.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Automatisch - Open Source Zapier Alternative
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
🧐 Automatisch is a business automation tool that lets you connect different services like Twitter, Slack, and more to automate your business processes.
|
||||||
|
|
||||||
|
💸 Automating your workflows doesn't have to be a difficult or expensive process. You also don't need any programming knowledge to use Automatisch.
|
||||||
|
|
||||||
|
## Advantages
|
||||||
|
|
||||||
|
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||||
|
|
||||||
|
✅ One of the main benefits of using Automatisch is that it allows you to store your data on your own servers, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||||
|
|
||||||
|
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
||||||
|
|
||||||
|
💙 No vendor lock-in. If you ever decide that Automatisch is no longer helpful for your business, you can switch to any other provider, which will be easier than switching from the one cloud provider to another since you have all data and flexibility.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The official documentation can be found here: [https://automatisch.io/docs](https://automatisch.io/docs)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/automatisch/automatisch.git
|
||||||
|
|
||||||
|
# Go to the repository folder
|
||||||
|
cd automatisch
|
||||||
|
|
||||||
|
# Start
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
||||||
|
|
||||||
|
For other installation types, you can check the [installation](https://automatisch.io/docs/guide/installation) guide.
|
||||||
|
|
||||||
|
## Community Links
|
||||||
|
|
||||||
|
- [Discord](https://discord.gg/dJSah9CVrC)
|
||||||
|
- [Twitter](https://twitter.com/automatischio)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you have any questions or problems, please visit our GitHub issues page, and we'll try to help you as soon as possible.
|
||||||
|
|
||||||
|
[https://github.com/automatisch/automatisch/issues](https://github.com/automatisch/automatisch/issues)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Automatisch Community Edition (Automatisch CE) is an open-source software with the [AGPL-3.0 license](LICENSE.agpl).
|
||||||
|
|
||||||
|
Automatisch Enterprise Edition (Automatisch EE) is a commercial offering with the [Enterprise license](LICENSE.enterprise).
|
||||||
|
|
||||||
|
The Automatisch repository contains both AGPL-licensed and Enterprise-licensed files. We maintain a single repository to make development easier.
|
||||||
|
|
||||||
|
All files that contain ".ee." in their name fall under the [Enterprise license](LICENSE.enterprise). All other files fall under the [AGPL-3.0 license](LICENSE.agpl).
|
||||||
|
|
||||||
|
See the [LICENSE](LICENSE) file for more information.
|
70
docker-compose.yml
Normal file
70
docker-compose.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
version: '3.9'
|
||||||
|
services:
|
||||||
|
main:
|
||||||
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile.compose
|
||||||
|
entrypoint: /compose-entrypoint.sh
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
- HOST=localhost
|
||||||
|
- PROTOCOL=http
|
||||||
|
- PORT=3000
|
||||||
|
- APP_ENV=production
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- POSTGRES_HOST=postgres
|
||||||
|
- POSTGRES_DATABASE=automatisch
|
||||||
|
- POSTGRES_USERNAME=automatisch_user
|
||||||
|
- POSTGRES_PASSWORD=automatisch_password
|
||||||
|
- ENCRYPTION_KEY
|
||||||
|
- WEBHOOK_SECRET_KEY
|
||||||
|
- APP_SECRET_KEY
|
||||||
|
volumes:
|
||||||
|
- automatisch_storage:/automatisch/storage
|
||||||
|
worker:
|
||||||
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile.compose
|
||||||
|
entrypoint: /compose-entrypoint.sh
|
||||||
|
depends_on:
|
||||||
|
- main
|
||||||
|
environment:
|
||||||
|
- APP_ENV=production
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- POSTGRES_HOST=postgres
|
||||||
|
- POSTGRES_DATABASE=automatisch
|
||||||
|
- POSTGRES_USERNAME=automatisch_user
|
||||||
|
- POSTGRES_PASSWORD=automatisch_password
|
||||||
|
- ENCRYPTION_KEY
|
||||||
|
- WEBHOOK_SECRET_KEY
|
||||||
|
- APP_SECRET_KEY
|
||||||
|
- WORKER=true
|
||||||
|
volumes:
|
||||||
|
- automatisch_storage:/automatisch/storage
|
||||||
|
postgres:
|
||||||
|
image: 'postgres:14.5'
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=automatisch
|
||||||
|
- POSTGRES_USER=automatisch_user
|
||||||
|
- POSTGRES_PASSWORD=automatisch_password
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
redis:
|
||||||
|
image: 'redis:7.0.4'
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
volumes:
|
||||||
|
automatisch_storage:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
25
docker/Dockerfile
Normal file
25
docker/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk --no-cache add --virtual build-dependencies python3 build-base git
|
||||||
|
|
||||||
|
WORKDIR /automatisch
|
||||||
|
|
||||||
|
# copy the app, note .dockerignore
|
||||||
|
COPY . /automatisch
|
||||||
|
|
||||||
|
RUN yarn
|
||||||
|
|
||||||
|
RUN cd packages/web && yarn build
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
rm -rf /usr/local/share/.cache/ && \
|
||||||
|
apk del build-dependencies
|
||||||
|
|
||||||
|
COPY ./docker/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
11
docker/Dockerfile.compose
Normal file
11
docker/Dockerfile.compose
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM automatischio/automatisch:latest
|
||||||
|
WORKDIR /automatisch
|
||||||
|
|
||||||
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
|
||||||
|
COPY ./compose-entrypoint.sh /compose-entrypoint.sh
|
||||||
|
RUN dos2unix /compose-entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["sh", "/compose-entrypoint.sh"]
|
26
docker/compose-entrypoint.sh
Executable file
26
docker/compose-entrypoint.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ ! -f /automatisch/storage/.env ]; then
|
||||||
|
>&2 echo "Saving environment variables"
|
||||||
|
ENCRYPTION_KEY="${ENCRYPTION_KEY:-$(openssl rand -base64 36)}"
|
||||||
|
WEBHOOK_SECRET_KEY="${WEBHOOK_SECRET_KEY:-$(openssl rand -base64 36)}"
|
||||||
|
APP_SECRET_KEY="${APP_SECRET_KEY:-$(openssl rand -base64 36)}"
|
||||||
|
echo "ENCRYPTION_KEY=$ENCRYPTION_KEY" >> /automatisch/storage/.env
|
||||||
|
echo "WEBHOOK_SECRET_KEY=$WEBHOOK_SECRET_KEY" >> /automatisch/storage/.env
|
||||||
|
echo "APP_SECRET_KEY=$APP_SECRET_KEY" >> /automatisch/storage/.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# initiate env. vars. from /automatisch/storage/.env file
|
||||||
|
export $(grep -v '^#' /automatisch/storage/.env | xargs)
|
||||||
|
|
||||||
|
# migration for webhook secret key, will be removed in the future.
|
||||||
|
if [[ -z "${WEBHOOK_SECRET_KEY}" ]]; then
|
||||||
|
WEBHOOK_SECRET_KEY="$(openssl rand -base64 36)"
|
||||||
|
echo "WEBHOOK_SECRET_KEY=$WEBHOOK_SECRET_KEY" >> /automatisch/storage/.env
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Environment variables have been set!"
|
||||||
|
|
||||||
|
sh /entrypoint.sh
|
13
docker/entrypoint.sh
Executable file
13
docker/entrypoint.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd packages/backend
|
||||||
|
|
||||||
|
if [ -n "$WORKER" ]; then
|
||||||
|
yarn start:worker
|
||||||
|
else
|
||||||
|
yarn db:migrate
|
||||||
|
yarn db:seed:user
|
||||||
|
yarn start
|
||||||
|
fi
|
@@ -2,14 +2,10 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"private": true,
|
"version": "0.10.0",
|
||||||
"version": "independent",
|
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
"publish": {
|
|
||||||
"registry": "http://localhost:5000"
|
|
||||||
},
|
|
||||||
"add": {
|
"add": {
|
||||||
"exact": true
|
"exact": true
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/root",
|
"name": "@automatisch/root",
|
||||||
|
"license": "See LICENSE file",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||||
"start:web": "lerna run --stream --scope=@*/web dev",
|
"start:web": "lerna run --stream --scope=@*/web dev",
|
||||||
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
"start:backend": "lerna run --stream --scope=@*/backend dev",
|
||||||
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} lint",
|
|
||||||
"build:watch": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend,cli} build:watch",
|
|
||||||
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
"build:docs": "cd ./packages/docs && yarn install && yarn build"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
@@ -17,13 +16,10 @@
|
|||||||
"**/babel-loader",
|
"**/babel-loader",
|
||||||
"**/webpack",
|
"**/webpack",
|
||||||
"**/@automatisch/web",
|
"**/@automatisch/web",
|
||||||
"**/@automatisch/types",
|
|
||||||
"**/ajv"
|
"**/ajv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
|
||||||
"@typescript-eslint/parser": "^5.9.1",
|
|
||||||
"eslint": "^8.13.0",
|
"eslint": "^8.13.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
@@ -31,6 +27,6 @@
|
|||||||
"prettier": "^2.5.1"
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "http://localhost:5000"
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
HOST=localhost
|
HOST=localhost
|
||||||
PROTOCOL=http
|
PROTOCOL=http
|
||||||
PORT=3000
|
PORT=3000
|
||||||
WEB_APP_URL=https://localhost:3001
|
WEB_APP_URL=http://localhost:3001
|
||||||
|
WEBHOOK_URL=http://localhost:3000
|
||||||
APP_ENV=development
|
APP_ENV=development
|
||||||
POSTGRES_DATABASE=automatisch_development
|
POSTGRES_DATABASE=automatisch_development
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
@@ -10,6 +11,12 @@ POSTGRES_USERNAME=automatish_development_user
|
|||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
POSTGRES_ENABLE_SSL=false
|
POSTGRES_ENABLE_SSL=false
|
||||||
ENCRYPTION_KEY=sample-encryption-key
|
ENCRYPTION_KEY=sample-encryption-key
|
||||||
|
WEBHOOK_SECRET_KEY=sample-webhook-key
|
||||||
APP_SECRET_KEY=sample-app-secret-key
|
APP_SECRET_KEY=sample-app-secret-key
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_USERNAME=redis_username
|
||||||
|
REDIS_PASSWORD=redis_password
|
||||||
|
REDIS_TLS=true
|
||||||
|
ENABLE_BULLMQ_DASHBOARD=false
|
||||||
|
SERVE_WEB_APP_SEPARATELY=true
|
||||||
|
15
packages/backend/.env-example.test
Normal file
15
packages/backend/.env-example.test
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV=test
|
||||||
|
HOST=localhost
|
||||||
|
PROTOCOL=http
|
||||||
|
PORT=3000
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
ENCRYPTION_KEY=sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY=sample_webhook_secret_key
|
||||||
|
APP_SECRET_KEY=sample_app_secret_key
|
||||||
|
POSTGRES_HOST=localhost
|
||||||
|
POSTGRES_DATABASE=automatisch_test
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USERNAME=automatisch_test_user
|
||||||
|
POSTGRES_PASSWORD=automatisch_test_user_password
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
AUTOMATISCH_CLOUD=true
|
@@ -3,6 +3,7 @@ dist
|
|||||||
build
|
build
|
||||||
coverage
|
coverage
|
||||||
packages/docs/*
|
packages/docs/*
|
||||||
|
packages/e2e-tests
|
||||||
|
|
||||||
.eslintrc.js
|
.eslintrc.js
|
||||||
husky.config.js
|
husky.config.js
|
12
packages/backend/.eslintrc.json
Normal file
12
packages/backend/.eslintrc.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "prettier"],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,4 @@
|
|||||||
# `backend`
|
# `backend`
|
||||||
|
|
||||||
> TODO: description
|
The open source Zapier alternative. Build workflow automation without spending
|
||||||
|
time and money.
|
||||||
## Usage
|
|
||||||
|
|
||||||
```
|
|
||||||
const backend = require('backend');
|
|
||||||
|
|
||||||
// TODO: DEMONSTRATE API
|
|
||||||
```
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Client } from 'pg';
|
import pg from 'pg';
|
||||||
|
|
||||||
const client = new Client({
|
const client = new pg.Client({
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
user: 'postgres',
|
user: 'postgres',
|
||||||
port: 5432,
|
port: 5432,
|
||||||
})
|
});
|
||||||
|
|
||||||
export default client;
|
export default client;
|
31
packages/backend/bin/database/convert-migrations.js
Normal file
31
packages/backend/bin/database/convert-migrations.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import appConfig from '../../src/config/app.js';
|
||||||
|
import logger from '../../src/helpers/logger.js';
|
||||||
|
import '../../src/config/orm.js';
|
||||||
|
import { client as knex } from '../../src/config/database.js';
|
||||||
|
|
||||||
|
export const renameMigrationsAsJsFiles = async () => {
|
||||||
|
if (!appConfig.isDev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tableExists = await knex.schema.hasTable('knex_migrations');
|
||||||
|
|
||||||
|
if (tableExists) {
|
||||||
|
await knex('knex_migrations')
|
||||||
|
.where('name', 'like', '%.ts')
|
||||||
|
.update({
|
||||||
|
name: knex.raw("REPLACE(name, '.ts', '.js')"),
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
`Migration file names with typescript renamed as JS file names!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
renameMigrationsAsJsFiles();
|
3
packages/backend/bin/database/create.js
Normal file
3
packages/backend/bin/database/create.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createDatabaseAndUser } from './utils.js';
|
||||||
|
|
||||||
|
createDatabaseAndUser();
|
@@ -1,3 +0,0 @@
|
|||||||
import { createDatabaseAndUser } from './utils';
|
|
||||||
|
|
||||||
createDatabaseAndUser();
|
|
3
packages/backend/bin/database/drop.js
Normal file
3
packages/backend/bin/database/drop.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { dropDatabase } from './utils.js';
|
||||||
|
|
||||||
|
dropDatabase();
|
@@ -1,3 +0,0 @@
|
|||||||
import { dropDatabase } from './utils';
|
|
||||||
|
|
||||||
dropDatabase();
|
|
3
packages/backend/bin/database/seed-user.js
Normal file
3
packages/backend/bin/database/seed-user.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createUser } from './utils.js';
|
||||||
|
|
||||||
|
createUser();
|
@@ -1,3 +0,0 @@
|
|||||||
import { createUser } from './utils';
|
|
||||||
|
|
||||||
createUser();
|
|
145
packages/backend/bin/database/utils.js
Normal file
145
packages/backend/bin/database/utils.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import appConfig from '../../src/config/app.js';
|
||||||
|
import logger from '../../src/helpers/logger.js';
|
||||||
|
import client from './client.js';
|
||||||
|
import User from '../../src/models/user.js';
|
||||||
|
import Config from '../../src/models/config.js';
|
||||||
|
import Role from '../../src/models/role.js';
|
||||||
|
import '../../src/config/orm.js';
|
||||||
|
import process from 'process';
|
||||||
|
|
||||||
|
async function fetchAdminRole() {
|
||||||
|
const role = await Role.query()
|
||||||
|
.where({
|
||||||
|
key: 'admin',
|
||||||
|
})
|
||||||
|
.limit(1)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUser(
|
||||||
|
email = 'user@automatisch.io',
|
||||||
|
password = 'sample'
|
||||||
|
) {
|
||||||
|
if (appConfig.disableSeedUser) {
|
||||||
|
logger.info('Seed user is disabled.');
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNIQUE_VIOLATION_CODE = '23505';
|
||||||
|
|
||||||
|
const role = await fetchAdminRole();
|
||||||
|
const userParams = {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
fullName: 'Initial admin',
|
||||||
|
roleId: role.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userCount = await User.query().resultSize();
|
||||||
|
|
||||||
|
if (userCount === 0) {
|
||||||
|
const user = await User.query().insertAndFetch(userParams);
|
||||||
|
logger.info(`User has been saved: ${user.email}`);
|
||||||
|
|
||||||
|
await Config.markInstallationCompleted();
|
||||||
|
} else {
|
||||||
|
logger.info('No need to seed a user.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err.nativeError.code !== UNIQUE_VIOLATION_CODE) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`User already exists: ${email}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDatabaseAndUser = async (
|
||||||
|
database = appConfig.postgresDatabase,
|
||||||
|
user = appConfig.postgresUsername
|
||||||
|
) => {
|
||||||
|
await client.connect();
|
||||||
|
await createDatabase(database);
|
||||||
|
await createDatabaseUser(user);
|
||||||
|
await grantPrivileges(database, user);
|
||||||
|
|
||||||
|
await client.end();
|
||||||
|
process.exit(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDatabase = async (database = appConfig.postgresDatabase) => {
|
||||||
|
const DUPLICATE_DB_CODE = '42P04';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.query(`CREATE DATABASE ${database}`);
|
||||||
|
logger.info(`Database: ${database} created!`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== DUPLICATE_DB_CODE) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Database: ${database} already exists!`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
|
||||||
|
const DUPLICATE_OBJECT_CODE = '42710';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await client.query(`CREATE USER ${user}`);
|
||||||
|
logger.info(`Database User: ${user} created!`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== DUPLICATE_OBJECT_CODE) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Database User: ${user} already exists!`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const grantPrivileges = async (
|
||||||
|
database = appConfig.postgresDatabase,
|
||||||
|
user = appConfig.postgresUsername
|
||||||
|
) => {
|
||||||
|
await client.query(
|
||||||
|
`GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};`
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`${user} has granted all privileges on ${database}!`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dropDatabase = async () => {
|
||||||
|
if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') {
|
||||||
|
const errorMessage =
|
||||||
|
'Drop database command can be used only with development or test environments!';
|
||||||
|
|
||||||
|
logger.error(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
await dropDatabaseAndUser();
|
||||||
|
|
||||||
|
await client.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dropDatabaseAndUser = async (
|
||||||
|
database = appConfig.postgresDatabase,
|
||||||
|
user = appConfig.postgresUsername
|
||||||
|
) => {
|
||||||
|
await client.query(`DROP DATABASE IF EXISTS ${database}`);
|
||||||
|
logger.info(`Database: ${database} removed!`);
|
||||||
|
|
||||||
|
await client.query(`DROP USER IF EXISTS ${user}`);
|
||||||
|
logger.info(`Database User: ${user} removed!`);
|
||||||
|
};
|
@@ -1,100 +0,0 @@
|
|||||||
import appConfig from '../../src/config/app';
|
|
||||||
import logger from '../../src/helpers/logger';
|
|
||||||
import client from './client';
|
|
||||||
import User from '../../src/models/user';
|
|
||||||
import '../../src/config/orm';
|
|
||||||
|
|
||||||
export async function createUser(email = 'user@automatisch.io', password = 'sample') {
|
|
||||||
const UNIQUE_VIOLATION_CODE = '23505';
|
|
||||||
const userParams = {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await User.query().insertAndFetch(userParams);
|
|
||||||
logger.info(`User has been saved: ${user.email}`);
|
|
||||||
} catch (err) {
|
|
||||||
if ((err as any).nativeError.code !== UNIQUE_VIOLATION_CODE) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`User already exists: ${email}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDatabaseAndUser = async (database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
|
|
||||||
await client.connect();
|
|
||||||
await createDatabase(database);
|
|
||||||
await createDatabaseUser(user);
|
|
||||||
await grantPrivileges(database, user);
|
|
||||||
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDatabase = async (database = appConfig.postgresDatabase) => {
|
|
||||||
const DUPLICATE_DB_CODE = '42P04';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.query(`CREATE DATABASE ${database}`);
|
|
||||||
logger.info(`Database: ${database} created!`);
|
|
||||||
} catch (err) {
|
|
||||||
if ((err as any).code !== DUPLICATE_DB_CODE) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Database: ${database} already exists!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
|
|
||||||
const DUPLICATE_OBJECT_CODE = '42710';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await client.query(`CREATE USER ${user}`);
|
|
||||||
logger.info(`Database User: ${user} created!`);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
if ((err as any).code !== DUPLICATE_OBJECT_CODE) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Database User: ${user} already exists!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const grantPrivileges = async (
|
|
||||||
database = appConfig.postgresDatabase, user = appConfig.postgresUsername
|
|
||||||
) => {
|
|
||||||
await client.query(
|
|
||||||
`GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};`
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`${user} has granted all privileges on ${database}!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dropDatabase = async () => {
|
|
||||||
if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') {
|
|
||||||
const errorMessage = 'Drop database command can be used only with development or test environments!'
|
|
||||||
|
|
||||||
logger.error(errorMessage)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.connect();
|
|
||||||
await dropDatabaseAndUser();
|
|
||||||
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dropDatabaseAndUser = async(database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
|
|
||||||
await client.query(`DROP DATABASE IF EXISTS ${database}`);
|
|
||||||
logger.info(`Database: ${database} removed!`);
|
|
||||||
|
|
||||||
await client.query(`DROP USER IF EXISTS ${user}`);
|
|
||||||
logger.info(`Database User: ${user} removed!`);
|
|
||||||
}
|
|
||||||
|
|
2
packages/backend/database.d.ts
vendored
2
packages/backend/database.d.ts
vendored
@@ -1,2 +0,0 @@
|
|||||||
export * as utils from './dist/bin/database/utils';
|
|
||||||
export * as database from './dist/src/config/database';
|
|
@@ -1,3 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
module.exports.utils = require('./dist/bin/database/utils');
|
|
||||||
module.exports.database = require('./dist/src/config/database');
|
|
@@ -1,6 +1,10 @@
|
|||||||
import appConfig from './src/config/app';
|
import { knexSnakeCaseMappers } from 'objection';
|
||||||
|
import appConfig from './src/config/app.js';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const fileExtension = appConfig.isDev ? 'ts' : 'js';
|
const fileExtension = 'js';
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
const knexConfig = {
|
const knexConfig = {
|
||||||
client: 'pg',
|
client: 'pg',
|
||||||
@@ -10,8 +14,10 @@ const knexConfig = {
|
|||||||
user: appConfig.postgresUsername,
|
user: appConfig.postgresUsername,
|
||||||
password: appConfig.postgresPassword,
|
password: appConfig.postgresPassword,
|
||||||
database: appConfig.postgresDatabase,
|
database: appConfig.postgresDatabase,
|
||||||
ssl: appConfig.postgresEnableSsl
|
ssl: appConfig.postgresEnableSsl,
|
||||||
},
|
},
|
||||||
|
asyncStackTraces: appConfig.isDev,
|
||||||
|
searchPath: [appConfig.postgresSchema],
|
||||||
pool: { min: 0, max: 20 },
|
pool: { min: 0, max: 20 },
|
||||||
migrations: {
|
migrations: {
|
||||||
directory: __dirname + '/src/db/migrations',
|
directory: __dirname + '/src/db/migrations',
|
||||||
@@ -20,7 +26,8 @@ const knexConfig = {
|
|||||||
},
|
},
|
||||||
seeds: {
|
seeds: {
|
||||||
directory: __dirname + '/src/db/seeds',
|
directory: __dirname + '/src/db/seeds',
|
||||||
}
|
},
|
||||||
}
|
...(appConfig.isTest ? knexSnakeCaseMappers() : {}),
|
||||||
|
};
|
||||||
|
|
||||||
export default knexConfig;
|
export default knexConfig;
|
1
packages/backend/logger.d.ts
vendored
1
packages/backend/logger.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export * from './dist/src/helpers/logger';
|
|
@@ -1,2 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
module.exports = require('./dist/src/helpers/logger');
|
|
@@ -1,65 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.1.0",
|
"version": "0.10.0",
|
||||||
"description": "> TODO: description",
|
"license": "See LICENSE file",
|
||||||
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec 'ts-node' src/server.ts --ext ts,json",
|
"dev": "nodemon --watch 'src/**/*.js' --exec 'node' src/server.js",
|
||||||
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
"worker": "nodemon --watch 'src/**/*.js' --exec 'node' src/worker.js",
|
||||||
"build": "tsc && yarn copy-statics",
|
"start": "node src/server.js",
|
||||||
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
"start:worker": "node src/worker.js",
|
||||||
"start": "node dist/src/server.js",
|
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||||
"test": "ava",
|
"test": "APP_ENV=test vitest run",
|
||||||
"lint": "eslint . --ignore-path ../../.eslintignore",
|
"lint": "eslint .",
|
||||||
"db:create": "ts-node ./bin/database/create.ts",
|
"db:create": "node ./bin/database/create.js",
|
||||||
"db:seed:user": "ts-node ./bin/database/seed-user.ts",
|
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||||
"db:drop": "ts-node ./bin/database/drop.ts",
|
"db:drop": "node ./bin/database/drop.js",
|
||||||
"db:migration:create": "knex migrate:make",
|
"db:migration:create": "knex migrate:make",
|
||||||
"db:rollback": "knex migrate:rollback",
|
"db:rollback": "knex migrate:rollback",
|
||||||
"db:migrate": "knex migrate:latest",
|
"db:migrate": "node ./bin/database/convert-migrations.js && knex migrate:latest"
|
||||||
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "0.1.0",
|
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
"@gitbeaker/node": "^35.6.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
"@graphql-tools/load": "^7.5.2",
|
"@graphql-tools/load": "^7.5.2",
|
||||||
"@octokit/oauth-methods": "^1.2.6",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||||
"@slack/bolt": "3.10.0",
|
"@sentry/node": "^7.42.0",
|
||||||
"@types/luxon": "^2.3.1",
|
"@sentry/tracing": "^7.42.0",
|
||||||
|
"accounting": "^0.4.1",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"axios": "0.24.0",
|
"axios": "1.6.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.1.0",
|
||||||
"bullmq": "^1.76.1",
|
"bullmq": "^3.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"discord.js": "13.2.0",
|
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "~4.16.1",
|
"express": "~4.18.2",
|
||||||
|
"express-async-handler": "^1.2.0",
|
||||||
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-graphql": "^0.12.0",
|
"express-graphql": "^0.12.0",
|
||||||
"flickr-sdk": "3.10.0",
|
"fast-xml-parser": "^4.0.11",
|
||||||
"googleapis": "89.0.0",
|
|
||||||
"graphql-middleware": "^6.1.15",
|
"graphql-middleware": "^6.1.15",
|
||||||
"graphql-shield": "^7.5.0",
|
"graphql-shield": "^7.5.0",
|
||||||
"graphql-tools": "^8.2.0",
|
"graphql-tools": "^8.2.0",
|
||||||
"graphql-type-json": "^0.3.2",
|
"handlebars": "^4.7.7",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"knex": "^0.95.11",
|
"https-proxy-agent": "^7.0.1",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
"knex": "^2.4.0",
|
||||||
|
"libphonenumber-js": "^1.10.48",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"luxon": "2.3.1",
|
"luxon": "2.5.2",
|
||||||
|
"memory-cache": "^0.2.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "1.4.5-lts.1",
|
||||||
|
"node-html-markdown": "^1.3.0",
|
||||||
"nodemailer": "6.7.0",
|
"nodemailer": "6.7.0",
|
||||||
|
"oauth-1.0a": "^2.2.6",
|
||||||
"objection": "^3.0.0",
|
"objection": "^3.0.0",
|
||||||
"octokit": "^1.7.1",
|
"passport": "^0.6.0",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"twilio": "3.70.0",
|
"php-serialize": "^4.0.2",
|
||||||
"twitch-js": "2.0.0-beta.42",
|
"pluralize": "^8.0.0",
|
||||||
"twitter-api-v2": "1.6.0",
|
"raw-body": "^2.5.2",
|
||||||
"winston": "^3.7.1"
|
"showdown": "^2.1.0",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"winston": "^3.7.1",
|
||||||
|
"xmlrpc": "^1.3.2"
|
||||||
},
|
},
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
@@ -68,7 +78,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/automatisch/automatisch#readme",
|
"homepage": "https://github.com/automatisch/automatisch#readme",
|
||||||
"main": "dist/src/app",
|
"main": "src/server",
|
||||||
"directories": {
|
"directories": {
|
||||||
"bin": "bin",
|
"bin": "bin",
|
||||||
"src": "src",
|
"src": "src",
|
||||||
@@ -76,13 +86,7 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"src",
|
"src"
|
||||||
"server.js",
|
|
||||||
"server.d.ts",
|
|
||||||
"logger.js",
|
|
||||||
"logger.d.ts",
|
|
||||||
"database.js",
|
|
||||||
"database.d.ts"
|
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -92,34 +96,12 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "0.1.0",
|
"node-gyp": "^10.1.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
|
||||||
"@types/bull": "^3.15.8",
|
|
||||||
"@types/cors": "^2.8.12",
|
|
||||||
"@types/crypto-js": "^4.0.2",
|
|
||||||
"@types/express": "^4.17.13",
|
|
||||||
"@types/http-errors": "^1.8.1",
|
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
|
||||||
"@types/lodash.get": "^4.4.6",
|
|
||||||
"@types/morgan": "^1.9.3",
|
|
||||||
"@types/node": "^16.10.2",
|
|
||||||
"@types/nodemailer": "^6.4.4",
|
|
||||||
"@types/pg": "^8.6.1",
|
|
||||||
"@types/pino": "^7.0.5",
|
|
||||||
"ava": "^3.15.0",
|
|
||||||
"nodemon": "^2.0.13",
|
"nodemon": "^2.0.13",
|
||||||
"sinon": "^11.1.2",
|
"supertest": "^6.3.3",
|
||||||
"ts-node": "^10.2.1"
|
"vitest": "^1.1.3"
|
||||||
},
|
},
|
||||||
"ava": {
|
"publishConfig": {
|
||||||
"files": [
|
"access": "public"
|
||||||
"test/**/*"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
"ts"
|
|
||||||
],
|
|
||||||
"require": [
|
|
||||||
"ts-node/register"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
packages/backend/server.d.ts
vendored
1
packages/backend/server.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export * from './dist/src/server';
|
|
@@ -1,2 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
module.exports = require('./dist/src/server.js');
|
|
70
packages/backend/src/app.js
Normal file
70
packages/backend/src/app.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import createError from 'http-errors';
|
||||||
|
import express from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
|
||||||
|
import appConfig from './config/app.js';
|
||||||
|
import corsOptions from './config/cors-options.js';
|
||||||
|
import morgan from './helpers/morgan.js';
|
||||||
|
import * as Sentry from './helpers/sentry.ee.js';
|
||||||
|
import appAssetsHandler from './helpers/app-assets-handler.js';
|
||||||
|
import webUIHandler from './helpers/web-ui-handler.js';
|
||||||
|
import errorHandler from './helpers/error-handler.js';
|
||||||
|
import './config/orm.js';
|
||||||
|
import {
|
||||||
|
createBullBoardHandler,
|
||||||
|
serverAdapter,
|
||||||
|
} from './helpers/create-bull-board-handler.js';
|
||||||
|
import injectBullBoardHandler from './helpers/inject-bull-board-handler.js';
|
||||||
|
import router from './routes/index.js';
|
||||||
|
import configurePassport from './helpers/passport.js';
|
||||||
|
|
||||||
|
createBullBoardHandler(serverAdapter);
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
Sentry.init(app);
|
||||||
|
|
||||||
|
Sentry.attachRequestHandler(app);
|
||||||
|
Sentry.attachTracingHandler(app);
|
||||||
|
|
||||||
|
injectBullBoardHandler(app, serverAdapter);
|
||||||
|
|
||||||
|
appAssetsHandler(app);
|
||||||
|
|
||||||
|
app.use(morgan);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
express.json({
|
||||||
|
limit: appConfig.requestBodySizeLimit,
|
||||||
|
verify(req, res, buf) {
|
||||||
|
req.rawBody = buf;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
express.urlencoded({
|
||||||
|
extended: true,
|
||||||
|
limit: appConfig.requestBodySizeLimit,
|
||||||
|
verify(req, res, buf) {
|
||||||
|
req.rawBody = buf;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
configurePassport(app);
|
||||||
|
|
||||||
|
app.use('/', router);
|
||||||
|
|
||||||
|
webUIHandler(app);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function (req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
Sentry.attachErrorHandler(app);
|
||||||
|
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
export default app;
|
@@ -1,45 +0,0 @@
|
|||||||
import appConfig from './config/app';
|
|
||||||
import createError from 'http-errors';
|
|
||||||
import express, { Request, Response, NextFunction } from 'express';
|
|
||||||
import cors from 'cors';
|
|
||||||
import corsOptions from './config/cors-options';
|
|
||||||
import graphQLInstance from './helpers/graphql-instance';
|
|
||||||
import morgan from './helpers/morgan';
|
|
||||||
import appAssetsHandler from './helpers/app-assets-handler';
|
|
||||||
import webUIHandler from './helpers/web-ui-handler';
|
|
||||||
import errorHandler from './helpers/error-handler';
|
|
||||||
import './config/orm';
|
|
||||||
import {
|
|
||||||
createBullBoardHandler,
|
|
||||||
serverAdapter,
|
|
||||||
} from './helpers/create-bull-board-handler';
|
|
||||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
|
||||||
|
|
||||||
if (appConfig.appEnv === 'development') {
|
|
||||||
createBullBoardHandler(serverAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
|
|
||||||
if (appConfig.appEnv === 'development') {
|
|
||||||
injectBullBoardHandler(app, serverAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
appAssetsHandler(app);
|
|
||||||
|
|
||||||
app.use(morgan);
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded({ extended: false }));
|
|
||||||
app.use(cors(corsOptions));
|
|
||||||
app.use('/graphql', graphQLInstance);
|
|
||||||
|
|
||||||
webUIHandler(app);
|
|
||||||
|
|
||||||
// catch 404 and forward to error handler
|
|
||||||
app.use(function (req: Request, res: Response, next: NextFunction) {
|
|
||||||
next(createError(404));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(errorHandler);
|
|
||||||
|
|
||||||
export default app;
|
|
@@ -0,0 +1,92 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create record',
|
||||||
|
key: 'createRecord',
|
||||||
|
description: 'Creates a new record with fields that automatically populate.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Base',
|
||||||
|
key: 'baseId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: 'Base in which to create the record.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listBases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Table',
|
||||||
|
key: 'tableId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.baseId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTables',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.baseId',
|
||||||
|
value: '{parameters.baseId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
additionalFields: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicFields',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFields',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.baseId',
|
||||||
|
value: '{parameters.baseId}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.tableId',
|
||||||
|
value: '{parameters.tableId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { baseId, tableId, ...rest } = $.step.parameters;
|
||||||
|
|
||||||
|
const fields = Object.entries(rest).reduce((result, [key, value]) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
result[key] = value.map((item) => item.value);
|
||||||
|
} else if (value !== '') {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
typecast: true,
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.post(`/v0/${baseId}/${tableId}`, body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
174
packages/backend/src/apps/airtable/actions/find-record/index.js
Normal file
174
packages/backend/src/apps/airtable/actions/find-record/index.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Find record',
|
||||||
|
key: 'findRecord',
|
||||||
|
description:
|
||||||
|
"Finds a record using simple field search or use Airtable's formula syntax to find a matching record.",
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Base',
|
||||||
|
key: 'baseId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: 'Base in which to create the record.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listBases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Table',
|
||||||
|
key: 'tableId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.baseId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTables',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.baseId',
|
||||||
|
value: '{parameters.baseId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search by field',
|
||||||
|
key: 'tableField',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.baseId', 'parameters.tableId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTableFields',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.baseId',
|
||||||
|
value: '{parameters.baseId}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.tableId',
|
||||||
|
value: '{parameters.tableId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search Value',
|
||||||
|
key: 'searchValue',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'The value of unique identifier for the record. For date values, please use the ISO format (e.g., "YYYY-MM-DD").',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search for exact match?',
|
||||||
|
key: 'exactMatch',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Yes', value: 'true' },
|
||||||
|
{ label: 'No', value: 'false' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search Formula',
|
||||||
|
key: 'searchFormula',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'Instead, you have the option to use an Airtable search formula for locating records according to sophisticated criteria and across various fields.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Limit to View',
|
||||||
|
key: 'limitToView',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.baseId', 'parameters.tableId'],
|
||||||
|
description:
|
||||||
|
'You have the choice to restrict the search to a particular view ID if desired.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTableViews',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.baseId',
|
||||||
|
value: '{parameters.baseId}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.tableId',
|
||||||
|
value: '{parameters.tableId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
baseId,
|
||||||
|
tableId,
|
||||||
|
tableField,
|
||||||
|
searchValue,
|
||||||
|
exactMatch,
|
||||||
|
searchFormula,
|
||||||
|
limitToView,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
let filterByFormula;
|
||||||
|
|
||||||
|
if (tableField && searchValue) {
|
||||||
|
filterByFormula =
|
||||||
|
exactMatch === 'true'
|
||||||
|
? `{${tableField}} = '${searchValue}'`
|
||||||
|
: `LOWER({${tableField}}) = LOWER('${searchValue}')`;
|
||||||
|
} else {
|
||||||
|
filterByFormula = searchFormula;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = new URLSearchParams({
|
||||||
|
filterByFormula,
|
||||||
|
view: limitToView,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v0/${baseId}/${tableId}/listRecords`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
4
packages/backend/src/apps/airtable/actions/index.js
Normal file
4
packages/backend/src/apps/airtable/actions/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import createRecord from './create-record/index.js';
|
||||||
|
import findRecord from './find-record/index.js';
|
||||||
|
|
||||||
|
export default [createRecord, findRecord];
|
9
packages/backend/src/apps/airtable/assets/favicon.svg
Normal file
9
packages/backend/src/apps/airtable/assets/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="256px" height="215px" viewBox="0 0 256 215" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||||
|
<g>
|
||||||
|
<path d="M114.25873,2.70101695 L18.8604023,42.1756384 C13.5552723,44.3711638 13.6102328,51.9065311 18.9486282,54.0225085 L114.746142,92.0117514 C123.163769,95.3498757 132.537419,95.3498757 140.9536,92.0117514 L236.75256,54.0225085 C242.08951,51.9065311 242.145916,44.3711638 236.83934,42.1756384 L141.442459,2.70101695 C132.738459,-0.900338983 122.961284,-0.900338983 114.25873,2.70101695" fill="#FFBF00"></path>
|
||||||
|
<path d="M136.349071,112.756863 L136.349071,207.659101 C136.349071,212.173089 140.900664,215.263892 145.096461,213.600615 L251.844122,172.166219 C254.281184,171.200072 255.879376,168.845451 255.879376,166.224705 L255.879376,71.3224678 C255.879376,66.8084791 251.327783,63.7176768 247.131986,65.3809537 L140.384325,106.815349 C137.94871,107.781496 136.349071,110.136118 136.349071,112.756863" fill="#26B5F8"></path>
|
||||||
|
<path d="M111.422771,117.65355 L79.742409,132.949912 L76.5257763,134.504714 L9.65047684,166.548104 C5.4112904,168.593211 0.000578531073,165.503855 0.000578531073,160.794612 L0.000578531073,71.7210757 C0.000578531073,70.0173017 0.874160452,68.5463864 2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill="#ED3049"></path>
|
||||||
|
<path d="M111.422771,117.65355 L79.742409,132.949912 L2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill-opacity="0.25" fill="#000000"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
38
packages/backend/src/apps/airtable/auth/generate-auth-url.js
Normal file
38
packages/backend/src/apps/airtable/auth/generate-auth-url.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import authScope from '../common/auth-scope.js';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const state = crypto.randomBytes(100).toString('base64url');
|
||||||
|
const codeVerifier = crypto.randomBytes(96).toString('base64url');
|
||||||
|
const codeChallenge = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(codeVerifier)
|
||||||
|
.digest('base64')
|
||||||
|
.replace(/=/g, '')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_');
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
response_type: 'code',
|
||||||
|
scope: authScope.join(' '),
|
||||||
|
state,
|
||||||
|
code_challenge: codeChallenge,
|
||||||
|
code_challenge_method: 'S256',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://airtable.com/oauth2/v1/authorize?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
originalCodeChallenge: codeChallenge,
|
||||||
|
originalState: state,
|
||||||
|
codeVerifier,
|
||||||
|
});
|
||||||
|
}
|
48
packages/backend/src/apps/airtable/auth/index.js
Normal file
48
packages/backend/src/apps/airtable/auth/index.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import refreshToken from './refresh-token.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/airtable/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Airtable, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
refreshToken,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
40
packages/backend/src/apps/airtable/auth/refresh-token.js
Normal file
40
packages/backend/src/apps/airtable/auth/refresh-token.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { URLSearchParams } from 'node:url';
|
||||||
|
|
||||||
|
import authScope from '../common/auth-scope.js';
|
||||||
|
|
||||||
|
const refreshToken = async ($) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: $.auth.data.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const basicAuthToken = Buffer.from(
|
||||||
|
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||||
|
).toString('base64');
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://airtable.com/oauth2/v1/token',
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Authorization: `Basic ${basicAuthToken}`,
|
||||||
|
},
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingAuthHeader: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshExpiresIn: data.refresh_expires_in,
|
||||||
|
scope: authScope.join(' '),
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default refreshToken;
|
@@ -0,0 +1,56 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
if ($.auth.data.originalState !== $.auth.data.state) {
|
||||||
|
throw new Error("The 'state' parameter does not match.");
|
||||||
|
}
|
||||||
|
if ($.auth.data.originalCodeChallenge !== $.auth.data.code_challenge) {
|
||||||
|
throw new Error("The 'code challenge' parameter does not match.");
|
||||||
|
}
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const basicAuthToken = Buffer.from(
|
||||||
|
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||||
|
).toString('base64');
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://airtable.com/oauth2/v1/token',
|
||||||
|
{
|
||||||
|
code: $.auth.data.code,
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
code_verifier: $.auth.data.codeVerifier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Authorization: `Basic ${basicAuthToken}`,
|
||||||
|
},
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingAuthHeader: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
clientId: $.auth.data.clientId,
|
||||||
|
clientSecret: $.auth.data.clientSecret,
|
||||||
|
scope: $.auth.data.scope,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshExpiresIn: data.refresh_expires_in,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
screenName: currentUser.email,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
12
packages/backend/src/apps/airtable/common/add-auth-header.js
Normal file
12
packages/backend/src/apps/airtable/common/add-auth-header.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if (
|
||||||
|
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
|
||||||
|
$.auth.data?.accessToken
|
||||||
|
) {
|
||||||
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
12
packages/backend/src/apps/airtable/common/auth-scope.js
Normal file
12
packages/backend/src/apps/airtable/common/auth-scope.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const authScope = [
|
||||||
|
'data.records:read',
|
||||||
|
'data.records:write',
|
||||||
|
'data.recordComments:read',
|
||||||
|
'data.recordComments:write',
|
||||||
|
'schema.bases:read',
|
||||||
|
'schema.bases:write',
|
||||||
|
'user.email:read',
|
||||||
|
'webhook:manage',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default authScope;
|
@@ -0,0 +1,6 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const { data: currentUser } = await $.http.get('/v0/meta/whoami');
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
6
packages/backend/src/apps/airtable/dynamic-data/index.js
Normal file
6
packages/backend/src/apps/airtable/dynamic-data/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import listBases from './list-bases/index.js';
|
||||||
|
import listTableFields from './list-table-fields/index.js';
|
||||||
|
import listTableViews from './list-table-views/index.js';
|
||||||
|
import listTables from './list-tables/index.js';
|
||||||
|
|
||||||
|
export default [listBases, listTableFields, listTableViews, listTables];
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List bases',
|
||||||
|
key: 'listBases',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const bases = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get('/v0/meta/bases', { params });
|
||||||
|
params.offset = data.offset;
|
||||||
|
|
||||||
|
if (data?.bases) {
|
||||||
|
for (const base of data.bases) {
|
||||||
|
bases.data.push({
|
||||||
|
value: base.id,
|
||||||
|
name: base.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return bases;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,39 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List table fields',
|
||||||
|
key: 'listTableFields',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tableFields = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const { baseId, tableId } = $.step.parameters;
|
||||||
|
|
||||||
|
if (!baseId) {
|
||||||
|
return tableFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
params.offset = data.offset;
|
||||||
|
|
||||||
|
if (data?.tables) {
|
||||||
|
for (const table of data.tables) {
|
||||||
|
if (table.id === tableId) {
|
||||||
|
table.fields.forEach((field) => {
|
||||||
|
tableFields.data.push({
|
||||||
|
value: field.name,
|
||||||
|
name: field.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return tableFields;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,39 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List table views',
|
||||||
|
key: 'listTableViews',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tableViews = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const { baseId, tableId } = $.step.parameters;
|
||||||
|
|
||||||
|
if (!baseId) {
|
||||||
|
return tableViews;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
params.offset = data.offset;
|
||||||
|
|
||||||
|
if (data?.tables) {
|
||||||
|
for (const table of data.tables) {
|
||||||
|
if (table.id === tableId) {
|
||||||
|
table.views.forEach((view) => {
|
||||||
|
tableViews.data.push({
|
||||||
|
value: view.id,
|
||||||
|
name: view.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return tableViews;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,35 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tables',
|
||||||
|
key: 'listTables',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tables = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const baseId = $.step.parameters.baseId;
|
||||||
|
|
||||||
|
if (!baseId) {
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
params.offset = data.offset;
|
||||||
|
|
||||||
|
if (data?.tables) {
|
||||||
|
for (const table of data.tables) {
|
||||||
|
tables.data.push({
|
||||||
|
value: table.id,
|
||||||
|
name: table.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,3 @@
|
|||||||
|
import listFields from './list-fields/index.js';
|
||||||
|
|
||||||
|
export default [listFields];
|
@@ -0,0 +1,86 @@
|
|||||||
|
const hasValue = (value) => value !== null && value !== undefined;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List fields',
|
||||||
|
key: 'listFields',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const options = [];
|
||||||
|
const { baseId, tableId } = $.step.parameters;
|
||||||
|
|
||||||
|
if (!hasValue(baseId) || !hasValue(tableId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`);
|
||||||
|
|
||||||
|
const selectedTable = data.tables.find((table) => table.id === tableId);
|
||||||
|
|
||||||
|
if (!selectedTable) return;
|
||||||
|
|
||||||
|
selectedTable.fields.forEach((field) => {
|
||||||
|
if (field.type === 'singleSelect') {
|
||||||
|
options.push({
|
||||||
|
label: field.name,
|
||||||
|
key: field.name,
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
options: field.options.choices.map((choice) => ({
|
||||||
|
label: choice.name,
|
||||||
|
value: choice.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} else if (field.type === 'multipleSelects') {
|
||||||
|
options.push({
|
||||||
|
label: field.name,
|
||||||
|
key: field.name,
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
key: 'value',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
options: field.options.choices.map((choice) => ({
|
||||||
|
label: choice.name,
|
||||||
|
value: choice.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else if (field.type === 'checkbox') {
|
||||||
|
options.push({
|
||||||
|
label: field.name,
|
||||||
|
key: field.name,
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.push({
|
||||||
|
label: field.name,
|
||||||
|
key: field.name,
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
},
|
||||||
|
};
|
22
packages/backend/src/apps/airtable/index.js
Normal file
22
packages/backend/src/apps/airtable/index.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
import dynamicFields from './dynamic-fields/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Airtable',
|
||||||
|
key: 'airtable',
|
||||||
|
baseUrl: 'https://airtable.com',
|
||||||
|
apiBaseUrl: 'https://api.airtable.com',
|
||||||
|
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
|
||||||
|
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
|
||||||
|
primaryColor: 'FFBF00',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
actions,
|
||||||
|
dynamicData,
|
||||||
|
dynamicFields,
|
||||||
|
});
|
1
packages/backend/src/apps/appwrite/assets/favicon.svg
Normal file
1
packages/backend/src/apps/appwrite/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="132" height="24" fill="none" viewBox="0 0 132 24"><path fill="#19191C" d="M38.557 19.495c2.16 0 3.25-1.113 3.725-1.87h.214c.094.805.664 1.562 1.779 1.562h2.111V16.82h-.545c-.38 0-.57-.213-.57-.545V6.776h-2.8v1.516h-.213c-.545-.758-1.684-1.824-3.772-1.824-3.321 0-5.789 2.748-5.789 6.514s2.515 6.513 5.86 6.513m.498-2.7c-1.969 0-3.51-1.445-3.51-3.79 0-2.297 1.494-3.86 3.487-3.86 1.898 0 3.487 1.397 3.487 3.86 0 2.108-1.352 3.79-3.463 3.79M48.04 24h2.799v-6.376h.213c.522.758 1.637 1.871 3.844 1.871 3.321 0 5.741-2.795 5.741-6.513 0-3.743-2.586-6.514-5.931-6.514-2.135 0-3.18 1.16-3.678 1.8h-.213V6.776h-2.776V24m6.263-7.134c-1.922 0-3.512-1.42-3.512-3.884 0-2.108 1.353-3.885 3.464-3.885 1.97 0 3.511 1.54 3.511 3.885 0 2.297-1.494 3.884-3.463 3.884M62.082 24h2.8v-6.376h.213c.522.758 1.637 1.871 3.843 1.871 3.321 0 5.51-2.795 5.51-6.513 0-3.743-2.355-6.514-5.7-6.514-2.135 0-3.179 1.16-3.677 1.8h-.214V6.776h-2.775zm6.263-7.134c-1.922 0-3.511-1.42-3.511-3.884 0-2.108 1.352-3.885 3.463-3.885 1.97 0 3.512 1.54 3.512 3.885 0 2.297-1.495 3.884-3.464 3.884m9.805 2.61h3.961l2.254-9.735h.143l2.253 9.735H90.7l3.153-12.412h-2.821l-2.254 9.759h-.214l-2.253-9.759h-3.725l-2.278 9.759h-.213l-2.23-9.759h-2.99l3.274 12.412m17.123 0h2.8V13.34c0-2.345 1.09-3.79 3.131-3.79h1.233V6.756h-.925c-1.59 0-2.8 1.09-3.274 2.132h-.19V7.064h-2.775zm21.057 0h2.183v-2.487h-2.159c-.854 0-1.21-.38-1.21-1.256V9.528h3.511V7.064h-3.511V3.582h-2.657v3.482h-2.325v2.464h2.159v6.229c0 2.63 1.589 3.719 4.009 3.719m9.693.019c2.586 0 4.864-1.279 5.67-3.86l-2.562-.616c-.451 1.373-1.755 2.084-3.131 2.084-2.041 0-3.393-1.326-3.417-3.41h9.419v-.782c0-3.695-2.301-6.443-6.097-6.443-3.346 0-6.216 2.63-6.216 6.537 0 3.79 2.538 6.49 6.334 6.49m-3.416-7.84c.166-1.492 1.518-2.747 3.298-2.747 1.708 0 3.108 1.066 3.25 2.747h-6.548"/><path fill="#19191C" fill-rule="evenodd" d="M108.916 19.476h-2.8V9.528h-2.182V7.064h4.982z" clip-rule="evenodd"/><path fill="#19191C" d="M107.309 5.342c1.02 0 1.779-.758 1.779-1.753 0-.971-.759-1.73-1.779-1.73-1.021 0-1.78.759-1.78 1.73 0 .995.759 1.753 1.78 1.753"/><path fill="#FD366E" d="M24.443 16.432v5.478H10.752c-3.989 0-7.472-2.203-9.335-5.478A11.041 11.041 0 0 1 0 11.695v-1.48a10.97 10.97 0 0 1 .381-2.247C1.661 3.368 5.82 0 10.751 0c4.934 0 9.092 3.37 10.371 7.967h-5.854c-.96-1.499-2.624-2.49-4.516-2.49s-3.555.991-4.516 2.49a5.47 5.47 0 0 0-.67 1.494 5.562 5.562 0 0 0-.202 1.494 5.5 5.5 0 0 0 1.69 3.983 5.32 5.32 0 0 0 3.698 1.494h13.69"/><path fill="#FD366E" d="M24.443 9.46v5.478h-9.994a5.5 5.5 0 0 0 1.691-3.983 5.56 5.56 0 0 0-.203-1.494h8.506"/></svg>
|
After Width: | Height: | Size: 2.6 KiB |
65
packages/backend/src/apps/appwrite/auth/index.js
Normal file
65
packages/backend/src/apps/appwrite/auth/index.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'Screen name of your connection to be used on Automatisch UI.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'projectId',
|
||||||
|
label: 'Project ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'Project ID of your Appwrite project.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'API key of your Appwrite project.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'instanceUrl',
|
||||||
|
label: 'Appwrite instance URL',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
placeholder: '',
|
||||||
|
description: '',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'host',
|
||||||
|
label: 'Host Name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'Host name of your Appwrite project.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,5 @@
|
|||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
await $.http.get('/v1/users');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
16
packages/backend/src/apps/appwrite/common/add-auth-header.js
Normal file
16
packages/backend/src/apps/appwrite/common/add-auth-header.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
requestConfig.headers['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
if ($.auth.data?.apiKey && $.auth.data?.projectId) {
|
||||||
|
requestConfig.headers['X-Appwrite-Project'] = $.auth.data.projectId;
|
||||||
|
requestConfig.headers['X-Appwrite-Key'] = $.auth.data.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($.auth.data?.host) {
|
||||||
|
requestConfig.headers['Host'] = $.auth.data.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
13
packages/backend/src/apps/appwrite/common/set-base-url.js
Normal file
13
packages/backend/src/apps/appwrite/common/set-base-url.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const setBaseUrl = ($, requestConfig) => {
|
||||||
|
const instanceUrl = $.auth.data.instanceUrl;
|
||||||
|
|
||||||
|
if (instanceUrl) {
|
||||||
|
requestConfig.baseURL = instanceUrl;
|
||||||
|
} else if ($.app.apiBaseUrl) {
|
||||||
|
requestConfig.baseURL = $.app.apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default setBaseUrl;
|
4
packages/backend/src/apps/appwrite/dynamic-data/index.js
Normal file
4
packages/backend/src/apps/appwrite/dynamic-data/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import listCollections from './list-collections/index.js';
|
||||||
|
import listDatabases from './list-databases/index.js';
|
||||||
|
|
||||||
|
export default [listCollections, listDatabases];
|
@@ -0,0 +1,44 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List collections',
|
||||||
|
key: 'listCollections',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const collections = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const databaseId = $.step.parameters.databaseId;
|
||||||
|
|
||||||
|
if (!databaseId) {
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
queries: [
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'orderAsc',
|
||||||
|
attribute: 'name',
|
||||||
|
}),
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'limit',
|
||||||
|
values: [100],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`/v1/databases/${databaseId}/collections`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.collections) {
|
||||||
|
for (const collection of data.collections) {
|
||||||
|
collections.data.push({
|
||||||
|
value: collection.$id,
|
||||||
|
name: collection.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,36 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List databases',
|
||||||
|
key: 'listDatabases',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const databases = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
queries: [
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'orderAsc',
|
||||||
|
attribute: 'name',
|
||||||
|
}),
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'limit',
|
||||||
|
values: [100],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/v1/databases', { params });
|
||||||
|
|
||||||
|
if (data?.databases) {
|
||||||
|
for (const database of data.databases) {
|
||||||
|
databases.data.push({
|
||||||
|
value: database.$id,
|
||||||
|
name: database.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases;
|
||||||
|
},
|
||||||
|
};
|
21
packages/backend/src/apps/appwrite/index.js
Normal file
21
packages/backend/src/apps/appwrite/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import setBaseUrl from './common/set-base-url.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import triggers from './triggers/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Appwrite',
|
||||||
|
key: 'appwrite',
|
||||||
|
baseUrl: 'https://appwrite.io',
|
||||||
|
apiBaseUrl: 'https://cloud.appwrite.io',
|
||||||
|
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
|
||||||
|
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
|
||||||
|
primaryColor: 'FD366E',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
dynamicData,
|
||||||
|
});
|
3
packages/backend/src/apps/appwrite/triggers/index.js
Normal file
3
packages/backend/src/apps/appwrite/triggers/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import newDocuments from './new-documents/index.js';
|
||||||
|
|
||||||
|
export default [newDocuments];
|
@@ -0,0 +1,104 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New documents',
|
||||||
|
key: 'newDocuments',
|
||||||
|
pollInterval: 15,
|
||||||
|
description: 'Triggers when a new document is created.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Database',
|
||||||
|
key: 'databaseId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDatabases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Collection',
|
||||||
|
key: 'collectionId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.databaseId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listCollections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.databaseId',
|
||||||
|
value: '{parameters.databaseId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { databaseId, collectionId } = $.step.parameters;
|
||||||
|
|
||||||
|
const limit = 1;
|
||||||
|
let lastDocumentId = undefined;
|
||||||
|
let offset = 0;
|
||||||
|
let documentCount = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const params = {
|
||||||
|
queries: [
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'orderDesc',
|
||||||
|
attribute: '$createdAt',
|
||||||
|
}),
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'limit',
|
||||||
|
values: [limit],
|
||||||
|
}),
|
||||||
|
// An invalid cursor shouldn't be sent.
|
||||||
|
lastDocumentId &&
|
||||||
|
JSON.stringify({
|
||||||
|
method: 'cursorAfter',
|
||||||
|
values: [lastDocumentId],
|
||||||
|
}),
|
||||||
|
].filter(Boolean),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`/v1/databases/${databaseId}/collections/${collectionId}/documents`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
|
||||||
|
const documents = data?.documents;
|
||||||
|
documentCount = documents?.length;
|
||||||
|
offset = offset + limit;
|
||||||
|
lastDocumentId = documents[documentCount - 1]?.$id;
|
||||||
|
|
||||||
|
if (!documentCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const document of documents) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: document,
|
||||||
|
meta: {
|
||||||
|
internalId: document.$id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (documentCount === limit);
|
||||||
|
},
|
||||||
|
});
|
216
packages/backend/src/apps/asana/actions/create-project/index.js
Normal file
216
packages/backend/src/apps/asana/actions/create-project/index.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
import omitBy from 'lodash/omitBy.js';
|
||||||
|
import isEmpty from 'lodash/isEmpty.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create project',
|
||||||
|
key: 'createProject',
|
||||||
|
description: 'Creates a new project.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Team',
|
||||||
|
key: 'teamId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTeams',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due date',
|
||||||
|
key: 'dueDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'Example due on: 2019-09-15',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Notes',
|
||||||
|
key: 'notes',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'You can format the notes using html.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Is the notes rich text?',
|
||||||
|
key: 'richText',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Default View',
|
||||||
|
key: 'defaultView',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
value: 'list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Board',
|
||||||
|
value: 'board',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Calendar',
|
||||||
|
value: 'calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Timeline',
|
||||||
|
value: 'timeline',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Owner',
|
||||||
|
key: 'ownerId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listUsers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Followers',
|
||||||
|
key: 'followerIds',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Follower',
|
||||||
|
key: 'followerId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listUsers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
workspaceId,
|
||||||
|
teamId,
|
||||||
|
dueDate,
|
||||||
|
name,
|
||||||
|
notes,
|
||||||
|
richText,
|
||||||
|
defaultView,
|
||||||
|
ownerId,
|
||||||
|
followerIds,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const allFollowers = followerIds
|
||||||
|
.map((followerId) => followerId.followerId)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
workspace: workspaceId,
|
||||||
|
team: teamId,
|
||||||
|
due_on: dueDate,
|
||||||
|
name,
|
||||||
|
default_view: defaultView,
|
||||||
|
owner: ownerId,
|
||||||
|
followers: allFollowers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (richText === 'true') {
|
||||||
|
data.html_notes = notes;
|
||||||
|
} else {
|
||||||
|
data.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = omitBy(data, isEmpty);
|
||||||
|
|
||||||
|
const response = await $.http.post('/1.0/projects', { data: filteredData });
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: response.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
312
packages/backend/src/apps/asana/actions/create-task/index.js
Normal file
312
packages/backend/src/apps/asana/actions/create-task/index.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
import omitBy from 'lodash/omitBy.js';
|
||||||
|
import isEmpty from 'lodash/isEmpty.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create task',
|
||||||
|
key: 'createTask',
|
||||||
|
description: 'Creates a new task.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Project',
|
||||||
|
key: 'projectId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listProjects',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Section',
|
||||||
|
key: 'sectionId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.projectId',
|
||||||
|
value: '{parameters.projectId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due date type',
|
||||||
|
key: 'dueDateType',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: "If not filled in, 'Date & time' will be assumed.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Date & time',
|
||||||
|
value: 'at',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Date only',
|
||||||
|
value: 'on',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due date (date & time)',
|
||||||
|
key: 'dueDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Example due at: 2019-09-15T02:06:58.147Z, example due on: 2019-09-15',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'You can format the description using html.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Is the description rich text?',
|
||||||
|
key: 'richText',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mark Task as complete?',
|
||||||
|
key: 'taskCompleted',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mark Task as liked?',
|
||||||
|
key: 'taskLiked',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Assignee',
|
||||||
|
key: 'assigneeId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listUsers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Followers',
|
||||||
|
key: 'followerIds',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Follower',
|
||||||
|
key: 'followerId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listUsers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tags',
|
||||||
|
key: 'tagIds',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Tag',
|
||||||
|
key: 'tagId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
workspaceId,
|
||||||
|
projectId,
|
||||||
|
sectionId,
|
||||||
|
dueDateType,
|
||||||
|
dueDate,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
richText,
|
||||||
|
taskCompleted,
|
||||||
|
taskLiked,
|
||||||
|
assigneeId,
|
||||||
|
followerIds,
|
||||||
|
tagIds,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const allFollowers = followerIds
|
||||||
|
.map((followerId) => followerId.followerId)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const allTags = tagIds.map((tagId) => tagId.tagId).filter(Boolean);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name,
|
||||||
|
completed: taskCompleted,
|
||||||
|
liked: taskLiked,
|
||||||
|
assignee: assigneeId,
|
||||||
|
assignee_section: sectionId,
|
||||||
|
followers: allFollowers,
|
||||||
|
projects: projectId,
|
||||||
|
tags: allTags,
|
||||||
|
workspace: workspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (richText === 'true') {
|
||||||
|
data.html_notes = description;
|
||||||
|
} else {
|
||||||
|
data.notes = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dueDateType === 'on') {
|
||||||
|
data.due_on = dueDate;
|
||||||
|
} else {
|
||||||
|
data.due_at = dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = omitBy(data, isEmpty);
|
||||||
|
|
||||||
|
const response = await $.http.post('/1.0/tasks', { data: filteredData });
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: response.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
4
packages/backend/src/apps/asana/actions/index.js
Normal file
4
packages/backend/src/apps/asana/actions/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import createProject from './create-project/index.js';
|
||||||
|
import createTask from './create-task/index.js';
|
||||||
|
|
||||||
|
export default [createProject, createTask];
|
8
packages/backend/src/apps/asana/assets/favicon.svg
Normal file
8
packages/backend/src/apps/asana/assets/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="555" height="110" viewBox="0 0 555 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M541.011 83.513C541.347 87.339 544.407 92.209 549.709 92.209H552.811C554.014 92.209 555 91.2232 555 90.0197V21.7948H554.986C554.923 20.6454 553.974 19.7248 552.811 19.7248H543.199C542.036 19.7248 541.087 20.6454 541.023 21.7948H541.011V27.3385C535.122 20.0789 525.836 17.0657 516.525 17.0657C495.359 17.0657 478.202 34.2365 478.202 55.419C478.202 76.6027 495.359 93.7741 516.525 93.7741V93.7759C525.836 93.7759 535.983 90.1606 541.01 83.5042L541.011 83.513V83.513ZM516.562 80.3509C503.101 80.3509 492.188 69.1896 492.188 55.4192C492.188 41.6511 503.101 30.4892 516.562 30.4892C530.022 30.4892 540.934 41.6511 540.934 55.4192C540.934 69.1896 530.022 80.3509 516.562 80.3509V80.3509Z" fill="#690031"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M466.05 85.8589L466.045 50.5554H466.046C466.046 30.655 453.501 17.2299 433.497 17.2299C423.947 17.2299 416.119 22.7561 413.355 27.5032C412.757 23.7913 410.788 19.8896 404.681 19.8896H401.569C400.365 19.8896 399.382 20.876 399.382 22.0795V83.6835C399.382 83.6853 399.382 83.69 399.382 83.6929V90.3102H399.394C399.457 91.4579 400.408 92.3796 401.57 92.3796H411.182C411.33 92.3796 411.474 92.3621 411.613 92.3347C411.677 92.3225 411.736 92.2975 411.798 92.28C411.869 92.2579 411.944 92.241 412.012 92.213C412.097 92.1775 412.175 92.1298 412.255 92.0855C412.294 92.0617 412.334 92.0448 412.372 92.0197C412.468 91.958 412.556 91.8835 412.641 91.8072C412.655 91.7932 412.672 91.7839 412.686 91.7711C412.781 91.6785 412.868 91.5766 412.946 91.4707C412.946 91.4689 412.946 91.4689 412.946 91.4689C413.187 91.1382 413.333 90.7399 413.357 90.3102H413.369V50.0081C413.369 39.3201 422.028 30.655 432.709 30.655C443.389 30.655 452.047 39.3201 452.047 50.0081L452.056 83.6952L452.058 83.6835C452.058 83.7132 452.063 83.7441 452.063 83.7761V90.3102H452.076C452.139 91.4579 453.089 92.3796 454.251 92.3796H463.864C464.012 92.3796 464.156 92.3621 464.295 92.3347C464.352 92.3243 464.404 92.3015 464.46 92.2858C464.538 92.2631 464.619 92.2433 464.695 92.213C464.773 92.1804 464.845 92.135 464.92 92.0931C464.965 92.0675 465.013 92.0489 465.056 92.0197C465.145 91.9615 465.226 91.8911 465.306 91.8212C465.326 91.8026 465.349 91.7886 465.368 91.7694C465.459 91.6814 465.54 91.5865 465.615 91.487C465.62 91.4794 465.626 91.4736 465.632 91.466C465.868 91.1382 466.013 90.7428 466.038 90.3161C466.038 90.3131 466.039 90.3102 466.039 90.3102H466.052V85.86L466.05 85.8589" fill="#690031"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M365.94 83.5127C366.276 87.3387 369.336 92.2088 374.638 92.2088H377.74C378.943 92.2088 379.927 91.223 379.927 90.0195V21.7945H379.915C379.852 20.6452 378.901 19.7246 377.74 19.7246H368.128C366.965 19.7246 366.016 20.6452 365.951 21.7945H365.94V27.3382C360.05 20.0786 350.764 17.0654 341.453 17.0654C320.288 17.0654 303.131 34.2362 303.131 55.4188C303.131 76.6025 320.288 93.7739 341.453 93.7739V93.7756C350.764 93.7756 360.912 90.1604 365.939 83.504L365.94 83.5127V83.5127ZM341.49 80.3506C328.03 80.3506 317.117 69.1893 317.117 55.4189C317.117 41.6509 328.03 30.489 341.49 30.489C354.952 30.489 365.862 41.6509 365.862 55.4189C365.862 69.1893 354.952 80.3506 341.49 80.3506V80.3506Z" fill="#690031"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M246.284 73.7415C252.702 78.1905 259.706 80.3513 266.437 80.3513C272.85 80.3513 279.479 77.0242 279.479 71.2337C279.479 63.5024 265.033 62.2995 255.957 59.2124C246.88 56.1252 239.061 49.7437 239.061 39.4092C239.061 23.5956 253.14 17.0645 266.281 17.0645C274.607 17.0645 283.198 19.8121 288.767 23.7482C290.686 25.2027 289.517 26.8726 289.517 26.8726L284.201 34.4716C283.603 35.3276 282.559 36.067 281.059 35.1407C279.559 34.2149 274.298 30.4884 266.281 30.4884C258.263 30.4884 253.434 34.1939 253.434 38.7868C253.434 44.2943 259.711 46.0266 267.063 47.9038C279.875 51.36 293.852 55.5144 293.852 71.2337C293.852 85.1665 280.829 93.777 266.437 93.777C255.53 93.777 246.244 90.6654 238.456 84.9459C236.834 83.3208 237.967 81.8121 237.967 81.8121L243.257 74.2515C244.334 72.8378 245.691 73.331 246.284 73.7415" fill="#690031"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M209.331 83.5127C209.668 87.3387 212.728 92.2088 218.03 92.2088H221.132C222.334 92.2088 223.32 91.223 223.32 90.0195V21.7945H223.307C223.244 20.6452 222.294 19.7246 221.132 19.7246H211.519C210.357 19.7246 209.408 20.6452 209.343 21.7945H209.331V27.3382C203.442 20.0786 194.156 17.0654 184.845 17.0654C163.68 17.0654 146.522 34.2362 146.522 55.4188C146.522 76.6025 163.68 93.7739 184.845 93.7739V93.7756C194.156 93.7756 204.304 90.1604 209.33 83.504L209.331 83.5127V83.5127ZM184.883 80.3506C171.422 80.3506 160.509 69.1893 160.509 55.4189C160.509 41.6509 171.422 30.489 184.883 30.489C198.343 30.489 209.255 41.6509 209.255 55.4189C209.255 69.1893 198.343 80.3506 184.883 80.3506V80.3506Z" fill="#690031"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.794 58.0274C78.5507 58.0274 67.0041 69.5741 67.0041 83.8185C67.0041 98.0618 78.5507 109.608 92.794 109.608C107.037 109.608 118.584 98.0618 118.584 83.8185C118.584 69.5741 107.037 58.0274 92.794 58.0274V58.0274ZM25.7899 58.0298C11.5466 58.0298 0 69.5741 0 83.8186C0 98.0618 11.5466 109.608 25.7899 109.608C40.0338 109.608 51.581 98.0618 51.581 83.8186C51.581 69.5741 40.0338 58.0298 25.7899 58.0298V58.0298ZM85.0815 25.7894C85.0815 40.0338 73.5354 51.5816 59.2921 51.5816C45.0483 51.5816 33.5022 40.0338 33.5022 25.7894C33.5022 11.5478 45.0483 0 59.2921 0C73.5354 0 85.0815 11.5478 85.0815 25.7894V25.7894Z" fill="#FF584A"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
25
packages/backend/src/apps/asana/auth/generate-auth-url.js
Normal file
25
packages/backend/src/apps/asana/auth/generate-auth-url.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const state = crypto.randomBytes(100).toString('base64url');
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
response_type: 'code',
|
||||||
|
//scope: authScope.join(' '),
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://app.asana.com/-/oauth_authorize?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
originalState: state,
|
||||||
|
});
|
||||||
|
}
|
48
packages/backend/src/apps/asana/auth/index.js
Normal file
48
packages/backend/src/apps/asana/auth/index.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import refreshToken from './refresh-token.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/asana/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Asana, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
refreshToken,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.data.email;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
37
packages/backend/src/apps/asana/auth/refresh-token.js
Normal file
37
packages/backend/src/apps/asana/auth/refresh-token.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { URLSearchParams } from 'node:url';
|
||||||
|
|
||||||
|
const refreshToken = async ($) => {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: $.auth.data.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://app.asana.com/-/oauth_token',
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingAuthHeader: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default refreshToken;
|
39
packages/backend/src/apps/asana/auth/verify-credentials.js
Normal file
39
packages/backend/src/apps/asana/auth/verify-credentials.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
if ($.auth.data.originalState !== $.auth.data.state) {
|
||||||
|
throw new Error("The 'state' parameter does not match.");
|
||||||
|
}
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://app.asana.com/-/oauth_token',
|
||||||
|
{
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
clientId: $.auth.data.clientId,
|
||||||
|
clientSecret: $.auth.data.clientSecret,
|
||||||
|
scope: $.auth.data.scope,
|
||||||
|
id: data.data.id,
|
||||||
|
gid: data.data.gid,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
screenName: `${data.data.name} - ${data.data.email}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
12
packages/backend/src/apps/asana/common/add-auth-header.js
Normal file
12
packages/backend/src/apps/asana/common/add-auth-header.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if (requestConfig.additionalProperties?.skipAddingAuthHeader)
|
||||||
|
return requestConfig;
|
||||||
|
|
||||||
|
if ($.auth.data?.accessToken) {
|
||||||
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,8 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const { data: currentUser } = await $.http.get(
|
||||||
|
`/1.0/users/${$.auth.data.gid}`
|
||||||
|
);
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
15
packages/backend/src/apps/asana/dynamic-data/index.js
Normal file
15
packages/backend/src/apps/asana/dynamic-data/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import listProjects from './list-projects/index.js';
|
||||||
|
import listSections from './list-sections/index.js';
|
||||||
|
import listTags from './list-tags/index.js';
|
||||||
|
import listTeams from './list-teams/index.js';
|
||||||
|
import listUsers from './list-users/index.js';
|
||||||
|
import listWorkspaces from './list-workspaces/index.js';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
listProjects,
|
||||||
|
listSections,
|
||||||
|
listTags,
|
||||||
|
listTeams,
|
||||||
|
listUsers,
|
||||||
|
listWorkspaces,
|
||||||
|
];
|
@@ -0,0 +1,42 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List projects',
|
||||||
|
key: 'listProjects',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const projects = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const workspaceId = $.step.parameters.workspaceId;
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
limit: 100,
|
||||||
|
offset: undefined,
|
||||||
|
workspace: workspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const {
|
||||||
|
data: { data, next_page },
|
||||||
|
} = await $.http.get('/1.0/projects', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
params.offset = next_page?.offset;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
for (const project of data) {
|
||||||
|
projects.data.push({
|
||||||
|
value: project.gid,
|
||||||
|
name: project.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List sections',
|
||||||
|
key: 'listSections',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const sections = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const projectId = $.step.parameters.projectId;
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
limit: 100,
|
||||||
|
offset: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const {
|
||||||
|
data: { data, next_page },
|
||||||
|
} = await $.http.get(`/1.0/projects/${projectId}/sections`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
params.offset = next_page?.offset;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
for (const section of data) {
|
||||||
|
sections.data.push({
|
||||||
|
value: section.gid,
|
||||||
|
name: section.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,42 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tags',
|
||||||
|
key: 'listTags',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tags = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const workspaceId = $.step.parameters.workspaceId;
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
return workspaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
limit: 100,
|
||||||
|
offset: undefined,
|
||||||
|
workspace: workspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const {
|
||||||
|
data: { data, next_page },
|
||||||
|
} = await $.http.get('/1.0/tags', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
params.offset = next_page?.offset;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
for (const tag of data) {
|
||||||
|
tags.data.push({
|
||||||
|
value: tag.gid,
|
||||||
|
name: tag.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.offset);
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user