Compare commits
	
		
			720 Commits
		
	
	
		
			hubspot-do
			...
			stringify-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ab4abd590a | ||
|   | 98e4b843ea | ||
|   | da2884d53c | ||
|   | 7f2937400a | ||
|   | a3a4a8e431 | ||
|   | 3249c954d3 | ||
|   | 7395d2a74e | ||
|   | 2060b7b49d | ||
|   | d263726c19 | ||
|   | 7e3325e959 | ||
|   | ec075f05c5 | ||
|   | 200e483574 | ||
|   | 6c11bfe93d | ||
|   | b4cc7f4d81 | ||
|   | 26fc63c52c | ||
|   | 116bf59b68 | ||
|   | 566f9dd5cc | ||
|   | ec740c07fa | ||
|   | 7506bf186b | ||
|   | 27c36b644d | ||
|   | ed2b1029f6 | ||
|   | 530a920517 | ||
|   | 42f8e635ed | ||
|   | 7ace67f906 | ||
|   | 1b437778dc | ||
|   | 43b0c65aab | ||
|   | 3f6a319ebe | ||
|   | 4cbd342e17 | ||
|   | 273f04128c | ||
|   | 9a5cef08d6 | ||
|   | bbecfdb718 | ||
|   | cbed79fbf1 | ||
|   | c4cbc024e6 | ||
|   | 2db8dbd5a3 | ||
|   | 13b995c9f2 | ||
|   | a0944193b6 | ||
|   | c7f343020a | ||
|   | 7a48ccc4f4 | ||
|   | cc1218b7a3 | ||
|   | 385e640a92 | ||
|   | b40a59cbef | ||
|   | 1a45ce5ea4 | ||
|   | ab05048409 | ||
|   | 47aabbe9c5 | ||
|   | 58cd2c522e | ||
|   | 1fe0cd9f84 | ||
|   | eeee1ba1a3 | ||
|   | 3cb8880c5c | ||
|   | 80cec86225 | ||
|   | cc65ed8fb0 | ||
|   | 0c3f1f4a5d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 76375941ca | ||
|   | d2a0415def | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 815c0834b2 | ||
|   | 7e1e1e2524 | ||
|   | 957d2793cb | ||
|   | 6ab86b7574 | ||
|   | 1c75b7226d | ||
|   | db22d8e2c9 | ||
|   | 7838b9609c | ||
|   | 2a1a0421b6 | ||
|   | 125ea0457e | ||
|   | c1f5f0632b | ||
|   | 8c84ab29c6 | ||
|   | 2767af11b2 | ||
|   | 59bbc4c182 | ||
|   | a755ee8dc1 | ||
|   | 4b1e66add3 | ||
|   | 4f6727810b | ||
|   | 60fdfc2b48 | ||
|   | 268d8c8b7d | ||
|   | 49d4071928 | ||
|   | c99b9dbe0a | ||
|   | 09d3a06b27 | ||
|   | 0d49bc003f | ||
|   | 86a5569bf7 | ||
|   | ad9fe7dec6 | ||
|   | 24bf07e068 | ||
|   | bae234827f | ||
|   | 81c698f45b | ||
|   | c9fecec575 | ||
|   | 2f42dfdc51 | ||
|   | 5bac68b0de | ||
|   | 5afd500c26 | ||
|   | 03f3e5f6ab | ||
|   | 50d91405a9 | ||
|   | 69eed65c9b | ||
|   | f1355cd0ab | ||
|   | cc1a924c8b | ||
|   | 02005a3f09 | ||
|   | d9219a5a48 | ||
|   | dffbdf544c | ||
|   | e2dbf1a215 | ||
|   | 66f9cb8d25 | ||
|   | c1396b97f0 | ||
|   | 920a711c00 | ||
|   | 02a872a376 | ||
|   | be4493710f | ||
|   | 52c0c5e0c5 | ||
|   | 4c639f170e | ||
|   | 509a414151 | ||
|   | 8f3c793a69 | ||
|   | a2dd9cf1b8 | ||
|   | 42285a5879 | ||
|   | 5d63fce6f0 | ||
|   | 46a4c8faec | ||
|   | ba14481151 | ||
|   | bdd8da98c4 | ||
|   | 5a4207414d | ||
|   | 730fdd32b1 | ||
|   | aa7f6694fc | ||
|   | 8fba6704df | ||
|   | 37d02eba02 | ||
|   | 75a87cf070 | ||
|   | 8acdc5853d | ||
|   | 1aa1f441b3 | ||
|   | 0e26032ac3 | ||
|   | 871f25c6d9 | ||
|   | d8199e7ba7 | ||
|   | 52b938eabe | ||
|   | 94fddf3d9b | ||
|   | 02c98c1ece | ||
|   | 6ba77667e9 | ||
|   | e201a5b806 | ||
|   | adac68c407 | ||
|   | d051275e54 | ||
|   | 2afc00364a | ||
|   | 0a1461231b | ||
|   | 129327f40d | ||
|   | 58819aad94 | ||
|   | 949a2543f5 | ||
|   | 0f9d732667 | ||
|   | b7df175950 | ||
|   | 4f7ce9874f | ||
|   | f63cc80383 | ||
|   | dd0a1328e8 | ||
|   | 27610c002c | ||
|   | 46ec9b5229 | ||
|   | 778559d537 | ||
|   | 1e64b8a903 | ||
|   | 0e000693d6 | ||
|   | cf9e09ea7a | ||
|   | eac2f729a5 | ||
|   | 9578cd27dd | ||
|   | 9304ffffc9 | ||
|   | 3fd628cb05 | ||
|   | fe68b70bd9 | ||
|   | ec2863d218 | ||
|   | dc56e7f883 | ||
|   | 2f1f537e00 | ||
|   | b7b3a3025b | ||
|   | edb9526538 | ||
|   | 62117ece06 | ||
|   | 913a2a0ac1 | ||
|   | 92ec3d07a3 | ||
|   | 67ee7899fd | ||
|   | b0ceb3fe7e | ||
|   | ba6b4c6854 | ||
|   | 61e90aed60 | ||
|   | 45b7a399f2 | ||
|   | 673ed25598 | ||
|   | 14fc460174 | ||
|   | 211b4537f9 | ||
|   | a683e059aa | ||
|   | d2d2cea567 | ||
|   | e5bda65a35 | ||
|   | 6aaef9df4b | ||
|   | 7fba4d72b0 | ||
|   | a6dc2ed4ba | ||
|   | 20a40eb2f6 | ||
|   | ec4ac9d075 | ||
|   | b2c14f4226 | ||
|   | 099dfbd0b0 | ||
|   | a9f5736c12 | ||
|   | 2bd4dd3ab0 | ||
|   | 186850c256 | ||
|   | 9922033d33 | ||
|   | 3c3e6e4144 | ||
|   | 0e4ac3b7f3 | ||
|   | c9813e0316 | ||
|   | 577c0cfab9 | ||
|   | 0966f9d715 | ||
|   | 3f5df118a0 | ||
|   | e05c1b26f1 | ||
|   | 725b38c697 | ||
|   | 402a0fdf3b | ||
|   | 078364ffa1 | ||
|   | f64d5ec4fc | ||
|   | 0666174501 | ||
|   | 12194a50e1 | ||
|   | 82ee592699 | ||
|   | 1b4fb2ce6e | ||
|   | ebea8d12d1 | ||
|   | f842dd77df | ||
|   | a6ec7a6c99 | ||
|   | 369c72282c | ||
|   | 6f30c1a509 | ||
|   | abfd1116c7 | ||
|   | 017854955d | ||
|   | 1405cddea1 | ||
|   | 00dd3164c9 | ||
|   | d5cbc0f611 | ||
|   | 5d2e9ccc67 | ||
|   | 017a881494 | ||
|   | 52994970e6 | ||
|   | ebae629e5c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0dea5150a0 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c99e8d270d | ||
|   | 4d79220b0c | ||
|   | 96fba7fbb8 | ||
|   | e0d610071d | ||
|   | ab0966c005 | ||
|   | 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 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 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 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 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 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 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 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 145172f486 | ||
|   | 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 | ||
|   | bd1ad5fa56 | ||
|   | f2e22e7445 | ||
|   | e2c5d843df | ||
|   | 557ea55ddf | ||
|   | 1591a4edd2 | ||
|   | b30b5024a8 | ||
|   | 8ef2bd3b8d | ||
|   | c413ab030b | ||
|   | 0f39007f92 | ||
|   | ab811daba7 | ||
|   | 1428bf8ffa | ||
|   | 53f7f38e23 | ||
|   | ce6ad9e0d3 | ||
|   | 17a0c6123a | ||
|   | 10edc5a8ad | ||
|   | bdf07d6bd5 | ||
|   | 10ff65e71a | ||
|   | 9395097fda | ||
|   | 47eb0e00e3 | ||
|   | 57d5f34ac5 | ||
|   | 06b040412a | ||
|   | cab040c74a | ||
|   | 21706a7d15 | ||
|   | 22e4b8aaeb | ||
|   | bd43a6021a | ||
|   | a90b58b6db | ||
|   | 92a9b096ec | ||
|   | 7a6aa99840 | ||
|   | 5657f0d793 | ||
|   | 798529007e | 
| @@ -28,7 +28,7 @@ cd packages/web | |||||||
| rm -rf .env | rm -rf .env | ||||||
| echo " | echo " | ||||||
| PORT=$WEB_PORT | PORT=$WEB_PORT | ||||||
| REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql | REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT | ||||||
| " >> .env | " >> .env | ||||||
| cd $CURRENT_DIR | cd $CURRENT_DIR | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,7 +36,6 @@ services: | |||||||
|   keycloak: |   keycloak: | ||||||
|     image: quay.io/keycloak/keycloak:21.1 |     image: quay.io/keycloak/keycloak:21.1 | ||||||
|     restart: always |     restart: always | ||||||
|     container_name: keycloak |  | ||||||
|     environment: |     environment: | ||||||
|       - KEYCLOAK_ADMIN=admin |       - KEYCLOAK_ADMIN=admin | ||||||
|       - KEYCLOAK_ADMIN_PASSWORD=admin |       - KEYCLOAK_ADMIN_PASSWORD=admin | ||||||
|   | |||||||
| @@ -4,5 +4,9 @@ | |||||||
| **/.devcontainer | **/.devcontainer | ||||||
| **/.github | **/.github | ||||||
| **/.vscode | **/.vscode | ||||||
|  | **/.env | ||||||
|  | **/.env.test | ||||||
|  | **/.env.production | ||||||
|  | **/yarn-error.log | ||||||
| packages/docs | packages/docs | ||||||
| packages/e2e-test | packages/e2e-test | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						| @@ -1,18 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
|   root: true, |  | ||||||
|   parser: '@typescript-eslint/parser', |  | ||||||
|   plugins: ['@typescript-eslint'], |  | ||||||
|   extends: [ |  | ||||||
|     'eslint:recommended', |  | ||||||
|     'plugin:@typescript-eslint/recommended', |  | ||||||
|     'prettier', |  | ||||||
|   ], |  | ||||||
|   overrides: [ |  | ||||||
|     { |  | ||||||
|       files: ['**/*.test.ts', '**/test/**/*.ts'], |  | ||||||
|       rules: { |  | ||||||
|         '@typescript-eslint/ban-ts-comment': ['off'], |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ], |  | ||||||
| }; |  | ||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -22,7 +22,7 @@ jobs: | |||||||
|       - 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 }}." | ||||||
|   start-backend-server: |   start-backend-server: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|   | |||||||
							
								
								
									
										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}`); | ||||||
|  |             } | ||||||
							
								
								
									
										8
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -62,17 +62,15 @@ jobs: | |||||||
|         run: yarn && yarn lerna bootstrap |         run: yarn && yarn lerna bootstrap | ||||||
|       - name: Install Playwright Browsers |       - name: Install Playwright Browsers | ||||||
|         run: yarn playwright install --with-deps |         run: yarn playwright install --with-deps | ||||||
|       - name: Build Automatisch |       - name: Build Automatisch web | ||||||
|         run: yarn lerna run --scope=@*/{web,cli} build |         working-directory: ./packages/web | ||||||
|  |         run: yarn build | ||||||
|         env: |         env: | ||||||
|           # Keep this until we clean up warnings in build processes |           # Keep this until we clean up warnings in build processes | ||||||
|           CI: false |           CI: false | ||||||
|       - name: Migrate database |       - name: Migrate database | ||||||
|         working-directory: ./packages/backend |         working-directory: ./packages/backend | ||||||
|         run: yarn db:migrate |         run: yarn db:migrate | ||||||
|       - name: Seed user |  | ||||||
|         working-directory: ./packages/backend |  | ||||||
|         run: yarn db:seed:user & |  | ||||||
|       - name: Install certutils |       - name: Install certutils | ||||||
|         run: sudo apt install -y libnss3-tools |         run: sudo apt install -y libnss3-tools | ||||||
|       - name: Install mkcert |       - name: Install mkcert | ||||||
|   | |||||||
| @@ -1,14 +1,25 @@ | |||||||
| # syntax=docker/dockerfile:1 | # syntax=docker/dockerfile:1 | ||||||
| FROM node:18-alpine | FROM node:18-alpine | ||||||
| WORKDIR /automatisch |  | ||||||
|  | ENV PORT=3000 | ||||||
|  |  | ||||||
|  | RUN \ | ||||||
|  |   apk --no-cache add --virtual build-dependencies python3 build-base git make g++ | ||||||
|  |  | ||||||
|  | WORKDIR /automatisch | ||||||
|  |  | ||||||
|  | # copy the app, note .dockerignore | ||||||
|  | COPY . /automatisch | ||||||
|  |  | ||||||
|  | RUN yarn | ||||||
|  |  | ||||||
|  | RUN cd packages/web && yarn build | ||||||
|  |  | ||||||
| RUN \ | RUN \ | ||||||
|   apk --no-cache add --virtual build-dependencies python3 build-base && \ |  | ||||||
|   yarn global add @automatisch/cli@0.10.0 --network-timeout 1000000 && \ |  | ||||||
|   rm -rf /usr/local/share/.cache/ && \ |   rm -rf /usr/local/share/.cache/ && \ | ||||||
|   apk del build-dependencies |   apk del build-dependencies | ||||||
|  |  | ||||||
| COPY ./entrypoint.sh /entrypoint.sh | COPY ./docker/entrypoint.sh /entrypoint.sh | ||||||
|  |  | ||||||
| EXPOSE 3000 | EXPOSE 3000 | ||||||
| ENTRYPOINT ["sh", "/entrypoint.sh"] | ENTRYPOINT ["sh", "/entrypoint.sh"] | ||||||
|   | |||||||
| @@ -1,24 +0,0 @@ | |||||||
| # syntax=docker/dockerfile:1 |  | ||||||
| FROM node:18-alpine |  | ||||||
|  |  | ||||||
| ENV PORT 3000 |  | ||||||
|  |  | ||||||
| RUN \ |  | ||||||
|   apk --no-cache add --virtual build-dependencies python3 build-base git |  | ||||||
|  |  | ||||||
| RUN git clone https://github.com/automatisch/automatisch.git |  | ||||||
|  |  | ||||||
| WORKDIR /automatisch |  | ||||||
|  |  | ||||||
| RUN yarn install |  | ||||||
|  |  | ||||||
| RUN if [ "$WORKER" != "true" ]; then cd packages/web && yarn build; fi |  | ||||||
|  |  | ||||||
| RUN \ |  | ||||||
|   rm -rf /usr/local/share/.cache/ && \ |  | ||||||
|   apk del build-dependencies |  | ||||||
|  |  | ||||||
| COPY ./docker/entrypoint-cloud.sh /entrypoint-cloud.sh |  | ||||||
|  |  | ||||||
| EXPOSE 3000 |  | ||||||
| ENTRYPOINT ["sh", "/entrypoint-cloud.sh"] |  | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # syntax=docker/dockerfile:1 | # syntax=docker/dockerfile:1 | ||||||
| FROM automatischio/automatisch:0.10.0 | FROM automatischio/automatisch:latest | ||||||
| WORKDIR /automatisch | WORKDIR /automatisch | ||||||
|  |  | ||||||
| RUN apk add --no-cache openssl dos2unix | RUN apk add --no-cache openssl dos2unix | ||||||
|   | |||||||
| @@ -1,13 +0,0 @@ | |||||||
| #!/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,8 +2,12 @@ | |||||||
|  |  | ||||||
| set -e | set -e | ||||||
|  |  | ||||||
|  | cd packages/backend | ||||||
|  |  | ||||||
| if [ -n "$WORKER" ]; then | if [ -n "$WORKER" ]; then | ||||||
|   automatisch start-worker |   yarn start:worker | ||||||
| else | else | ||||||
|   automatisch start |   yarn db:migrate | ||||||
|  |   yarn db:seed:user | ||||||
|  |   yarn start | ||||||
| fi | fi | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
|     "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} lint", |  | ||||||
|     "build:docs": "cd ./packages/docs && yarn install && yarn build" |     "build:docs": "cd ./packages/docs && yarn install && yarn build" | ||||||
|   }, |   }, | ||||||
|   "workspaces": { |   "workspaces": { | ||||||
| @@ -21,8 +20,6 @@ | |||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "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", | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import appConfig from '../../src/config/app.js'; | |||||||
| import logger from '../../src/helpers/logger.js'; | import logger from '../../src/helpers/logger.js'; | ||||||
| import client from './client.js'; | import client from './client.js'; | ||||||
| import User from '../../src/models/user.js'; | import User from '../../src/models/user.js'; | ||||||
|  | import Config from '../../src/models/config.js'; | ||||||
| import Role from '../../src/models/role.js'; | import Role from '../../src/models/role.js'; | ||||||
| import '../../src/config/orm.js'; | import '../../src/config/orm.js'; | ||||||
| import process from 'process'; | import process from 'process'; | ||||||
| @@ -21,6 +22,14 @@ export async function createUser( | |||||||
|   email = 'user@automatisch.io', |   email = 'user@automatisch.io', | ||||||
|   password = 'sample' |   password = 'sample' | ||||||
| ) { | ) { | ||||||
|  |   if (appConfig.disableSeedUser) { | ||||||
|  |     logger.info('Seed user is disabled.'); | ||||||
|  |  | ||||||
|  |     process.exit(0); | ||||||
|  |  | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   const UNIQUE_VIOLATION_CODE = '23505'; |   const UNIQUE_VIOLATION_CODE = '23505'; | ||||||
|  |  | ||||||
|   const role = await fetchAdminRole(); |   const role = await fetchAdminRole(); | ||||||
| @@ -37,6 +46,8 @@ export async function createUser( | |||||||
|     if (userCount === 0) { |     if (userCount === 0) { | ||||||
|       const user = await User.query().insertAndFetch(userParams); |       const user = await User.query().insertAndFetch(userParams); | ||||||
|       logger.info(`User has been saved: ${user.email}`); |       logger.info(`User has been saved: ${user.email}`); | ||||||
|  |  | ||||||
|  |       await Config.markInstallationCompleted(); | ||||||
|     } else { |     } else { | ||||||
|       logger.info('No need to seed a user.'); |       logger.info('No need to seed a user.'); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { knexSnakeCaseMappers } from 'objection'; | import { knexSnakeCaseMappers } from 'objection'; | ||||||
| import appConfig from './src/config/app.js'; | import appConfig from './src/config/app.js'; | ||||||
| import path from 'path'; | import path, { join } from 'path'; | ||||||
| import { fileURLToPath } from 'url'; | import { fileURLToPath } from 'url'; | ||||||
|  |  | ||||||
| const fileExtension = 'js'; | const fileExtension = 'js'; | ||||||
| @@ -20,12 +20,12 @@ const knexConfig = { | |||||||
|   searchPath: [appConfig.postgresSchema], |   searchPath: [appConfig.postgresSchema], | ||||||
|   pool: { min: 0, max: 20 }, |   pool: { min: 0, max: 20 }, | ||||||
|   migrations: { |   migrations: { | ||||||
|     directory: __dirname + '/src/db/migrations', |     directory: join(__dirname, '/src/db/migrations'), | ||||||
|     extension: fileExtension, |     extension: fileExtension, | ||||||
|     loadExtensions: [`.${fileExtension}`], |     loadExtensions: [`.${fileExtension}`], | ||||||
|   }, |   }, | ||||||
|   seeds: { |   seeds: { | ||||||
|     directory: __dirname + '/src/db/seeds', |     directory: join(__dirname, '/src/db/seeds'), | ||||||
|   }, |   }, | ||||||
|   ...(appConfig.isTest ? knexSnakeCaseMappers() : {}), |   ...(appConfig.isTest ? knexSnakeCaseMappers() : {}), | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -5,13 +5,13 @@ | |||||||
|   "description": "The open source Zapier alternative. Build workflow automation without spending time and money.", |   "description": "The open source Zapier alternative. Build workflow automation without spending time and money.", | ||||||
|   "type": "module", |   "type": "module", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "dev": "nodemon --watch 'src/**/*.js' --exec 'node' src/server.js", |     "dev": "nodemon --exec node src/server.js", | ||||||
|     "worker": "nodemon --watch 'src/**/*.js' --exec 'node' src/worker.js", |     "worker": "nodemon --exec node src/worker.js", | ||||||
|     "start": "node src/server.js", |     "start": "node src/server.js", | ||||||
|     "start:worker": "node src/worker.js", |     "start:worker": "node src/worker.js", | ||||||
|     "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", |     "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", | ||||||
|     "test": "APP_ENV=test vitest run", |     "test": "APP_ENV=test vitest run", | ||||||
|     "lint": "eslint . --ignore-path ../../.eslintignore", |     "lint": "eslint .", | ||||||
|     "db:create": "node ./bin/database/create.js", |     "db:create": "node ./bin/database/create.js", | ||||||
|     "db:seed:user": "node ./bin/database/seed-user.js", |     "db:seed:user": "node ./bin/database/seed-user.js", | ||||||
|     "db:drop": "node ./bin/database/drop.js", |     "db:drop": "node ./bin/database/drop.js", | ||||||
| @@ -31,7 +31,7 @@ | |||||||
|     "accounting": "^0.4.1", |     "accounting": "^0.4.1", | ||||||
|     "ajv-formats": "^2.1.1", |     "ajv-formats": "^2.1.1", | ||||||
|     "axios": "1.6.0", |     "axios": "1.6.0", | ||||||
|     "bcrypt": "^5.0.1", |     "bcrypt": "^5.1.0", | ||||||
|     "bullmq": "^3.0.0", |     "bullmq": "^3.0.0", | ||||||
|     "cors": "^2.8.5", |     "cors": "^2.8.5", | ||||||
|     "crypto-js": "^4.1.1", |     "crypto-js": "^4.1.1", | ||||||
| @@ -49,6 +49,7 @@ | |||||||
|     "http-errors": "~1.6.3", |     "http-errors": "~1.6.3", | ||||||
|     "http-proxy-agent": "^7.0.0", |     "http-proxy-agent": "^7.0.0", | ||||||
|     "https-proxy-agent": "^7.0.1", |     "https-proxy-agent": "^7.0.1", | ||||||
|  |     "isolated-vm": "^5.0.1", | ||||||
|     "jsonwebtoken": "^9.0.0", |     "jsonwebtoken": "^9.0.0", | ||||||
|     "knex": "^2.4.0", |     "knex": "^2.4.0", | ||||||
|     "libphonenumber-js": "^1.10.48", |     "libphonenumber-js": "^1.10.48", | ||||||
| @@ -67,6 +68,7 @@ | |||||||
|     "pluralize": "^8.0.0", |     "pluralize": "^8.0.0", | ||||||
|     "raw-body": "^2.5.2", |     "raw-body": "^2.5.2", | ||||||
|     "showdown": "^2.1.0", |     "showdown": "^2.1.0", | ||||||
|  |     "uuid": "^9.0.1", | ||||||
|     "winston": "^3.7.1", |     "winston": "^3.7.1", | ||||||
|     "xmlrpc": "^1.3.2" |     "xmlrpc": "^1.3.2" | ||||||
|   }, |   }, | ||||||
| @@ -95,12 +97,16 @@ | |||||||
|     "url": "https://github.com/automatisch/automatisch/issues" |     "url": "https://github.com/automatisch/automatisch/issues" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@typescript-eslint/utils": "^7.0.2", |     "node-gyp": "^10.1.0", | ||||||
|     "nodemon": "^2.0.13", |     "nodemon": "^2.0.13", | ||||||
|     "supertest": "^6.3.3", |     "supertest": "^6.3.3", | ||||||
|     "vitest": "^1.1.3" |     "vitest": "^1.1.3" | ||||||
|   }, |   }, | ||||||
|   "publishConfig": { |   "publishConfig": { | ||||||
|     "access": "public" |     "access": "public" | ||||||
|  |   }, | ||||||
|  |   "nodemonConfig": { | ||||||
|  |     "watch": [ "src/" ], | ||||||
|  |     "ext": "js" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -11,7 +11,7 @@ export default defineApp({ | |||||||
|     'https://azure.microsoft.com/en-us/products/ai-services/openai-service', |     'https://azure.microsoft.com/en-us/products/ai-services/openai-service', | ||||||
|   apiBaseUrl: '', |   apiBaseUrl: '', | ||||||
|   iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg', |   iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg', | ||||||
|   authDocUrl: 'https://automatisch.io/docs/apps/azure-openai/connection', |   authDocUrl: '{DOCS_URL}/apps/azure-openai/connection', | ||||||
|   primaryColor: '000000', |   primaryColor: '000000', | ||||||
|   supportsConnections: true, |   supportsConnections: true, | ||||||
|   beforeRequest: [setBaseUrl, addAuthHeader], |   beforeRequest: [setBaseUrl, addAuthHeader], | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ export default defineAction({ | |||||||
|     'Creates an attachment of a specified object by given parent ID.', |     'Creates an attachment of a specified object by given parent ID.', | ||||||
|   arguments: [ |   arguments: [ | ||||||
|     { |     { | ||||||
|       label: 'Templete Data', |       label: 'Template Data', | ||||||
|       key: 'templateData', |       key: 'templateData', | ||||||
|       type: 'string', |       type: 'string', | ||||||
|       required: true, |       required: true, | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ export default defineApp({ | |||||||
|   name: 'Carbone', |   name: 'Carbone', | ||||||
|   key: 'carbone', |   key: 'carbone', | ||||||
|   iconUrl: '{BASE_URL}/apps/carbone/assets/favicon.svg', |   iconUrl: '{BASE_URL}/apps/carbone/assets/favicon.svg', | ||||||
|   authDocUrl: 'https://automatisch.io/docs/apps/carbone/connection', |   authDocUrl: '{DOCS_URL}/apps/carbone/connection', | ||||||
|   supportsConnections: true, |   supportsConnections: true, | ||||||
|   baseUrl: 'https://carbone.io', |   baseUrl: 'https://carbone.io', | ||||||
|   apiBaseUrl: 'https://api.carbone.io', |   apiBaseUrl: 'https://api.carbone.io', | ||||||
|   | |||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create folder', | ||||||
|  |   key: 'createFolder', | ||||||
|  |   description: 'Creates a new folder.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder Name', | ||||||
|  |       key: 'folderName', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { spaceId, folderName } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const body = { | ||||||
|  |       name: folderName, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post(`/v2/space/${spaceId}/folder`, body); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										135
									
								
								packages/backend/src/apps/clickup/actions/create-list/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,135 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create list', | ||||||
|  |   key: 'createList', | ||||||
|  |   description: 'Creates a new list.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder', | ||||||
|  |       key: 'folderId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.spaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFolders', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.spaceId', | ||||||
|  |             value: '{parameters.spaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'List Name', | ||||||
|  |       key: 'listName', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'List Info', | ||||||
|  |       key: 'listInfo', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Priority', | ||||||
|  |       key: 'priority', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Urgent', value: 1 }, | ||||||
|  |         { label: 'High', value: 2 }, | ||||||
|  |         { label: 'Normal', value: 3 }, | ||||||
|  |         { label: 'Low', value: 4 }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Due Date', | ||||||
|  |       key: 'dueDate', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: 'format: integer <int64>', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { folderId, listName, listInfo, priority, dueDate } = | ||||||
|  |       $.step.parameters; | ||||||
|  |  | ||||||
|  |     const body = { | ||||||
|  |       name: listName, | ||||||
|  |       content: listInfo, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if (priority) { | ||||||
|  |       body.priority = priority; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dueDate) { | ||||||
|  |       body.due_date = dueDate; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post(`/v2/folder/${folderId}/list`, body); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										294
									
								
								packages/backend/src/apps/clickup/actions/create-task/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,294 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.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: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder', | ||||||
|  |       key: 'folderId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.spaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFolders', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.spaceId', | ||||||
|  |             value: '{parameters.spaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'List', | ||||||
|  |       key: 'listId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       dependsOn: ['parameters.folderId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listLists', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.folderId', | ||||||
|  |             value: '{parameters.folderId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Task Name', | ||||||
|  |       key: 'taskName', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Task Description', | ||||||
|  |       key: 'taskDescription', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Markdown Content', | ||||||
|  |       key: 'markdownContent', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'False', value: 'false' }, | ||||||
|  |         { label: 'True', value: 'true' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Assignees', | ||||||
|  |       key: 'assigneeIds', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Assignee', | ||||||
|  |           key: 'assigneeId', | ||||||
|  |           type: 'dropdown', | ||||||
|  |           required: false, | ||||||
|  |           dependsOn: ['parameters.listId'], | ||||||
|  |           variables: true, | ||||||
|  |           source: { | ||||||
|  |             type: 'query', | ||||||
|  |             name: 'getDynamicData', | ||||||
|  |             arguments: [ | ||||||
|  |               { | ||||||
|  |                 name: 'key', | ||||||
|  |                 value: 'listAssignees', | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 name: 'parameters.listId', | ||||||
|  |                 value: '{parameters.listId}', | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Task Status', | ||||||
|  |       key: 'taskStatus', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.listId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listStatuses', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.listId', | ||||||
|  |             value: '{parameters.listId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Tags', | ||||||
|  |       key: 'tagIds', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'tag', | ||||||
|  |           key: 'tagId', | ||||||
|  |           type: 'dropdown', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |           source: { | ||||||
|  |             type: 'query', | ||||||
|  |             name: 'getDynamicData', | ||||||
|  |             arguments: [ | ||||||
|  |               { | ||||||
|  |                 name: 'key', | ||||||
|  |                 value: 'listTags', | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Priority', | ||||||
|  |       key: 'priority', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Urgent', value: 1 }, | ||||||
|  |         { label: 'High', value: 2 }, | ||||||
|  |         { label: 'Normal', value: 3 }, | ||||||
|  |         { label: 'Low', value: 4 }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Due Date', | ||||||
|  |       key: 'dueDate', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: 'format: integer <int64>', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Start Date', | ||||||
|  |       key: 'startDate', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: 'format: integer <int64>', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { | ||||||
|  |       listId, | ||||||
|  |       taskName, | ||||||
|  |       taskDescription, | ||||||
|  |       markdownContent, | ||||||
|  |       assigneeIds, | ||||||
|  |       taskStatus, | ||||||
|  |       tagIds, | ||||||
|  |       priority, | ||||||
|  |       dueDate, | ||||||
|  |       startDate, | ||||||
|  |     } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const tags = tagIds.map((tag) => tag.tagId); | ||||||
|  |     const assignees = assigneeIds.map((assignee) => | ||||||
|  |       Number(assignee.assigneeId) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     const body = { | ||||||
|  |       name: taskName, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if (assignees.length) { | ||||||
|  |       body.assignees = assignees; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (taskStatus) { | ||||||
|  |       body.status = taskStatus; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (tags.length) { | ||||||
|  |       body.tags = tags; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (priority) { | ||||||
|  |       body.priority = priority; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dueDate) { | ||||||
|  |       body.due_date = dueDate; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (startDate) { | ||||||
|  |       body.start_date = startDate; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (markdownContent) { | ||||||
|  |       body.markdown_description = taskDescription; | ||||||
|  |     } else { | ||||||
|  |       body.description = taskDescription; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post(`/v2/list/${listId}/task`, body); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,82 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Find task by id', | ||||||
|  |   key: 'findTaskById', | ||||||
|  |   description: 'Finds a task using id.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Task ID', | ||||||
|  |       key: 'taskId', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Use Custom ID', | ||||||
|  |       key: 'useCustomId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { | ||||||
|  |           label: 'True', | ||||||
|  |           value: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'False', | ||||||
|  |           value: false, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |       additionalFields: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicFields', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFieldsWhenUsingCustomId', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.useCustomId', | ||||||
|  |             value: '{parameters.useCustomId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Include Subtasks?', | ||||||
|  |       key: 'includeSubtasks', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { | ||||||
|  |           label: 'True', | ||||||
|  |           value: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'False', | ||||||
|  |           value: false, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { taskId, useCustomId, includeSubtasks } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       custom_task_ids: useCustomId || false, | ||||||
|  |       include_subtasks: includeSubtasks, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/task/${taskId}`, { params }); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										6
									
								
								packages/backend/src/apps/clickup/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | |||||||
|  | import createFolder from './create-folder/index.js'; | ||||||
|  | import createList from './create-list/index.js'; | ||||||
|  | import createTask from './create-task/index.js'; | ||||||
|  | import findTaskById from './find-task-by-id/index.js'; | ||||||
|  |  | ||||||
|  | export default [createFolder, createList, createTask, findTaskById]; | ||||||
							
								
								
									
										27
									
								
								packages/backend/src/apps/clickup/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | |||||||
|  | <svg width="185" height="185" viewBox="0 0 185 185" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  | <g filter="url(#filter0_d)"> | ||||||
|  | <rect x="30" y="20" width="125" height="125" rx="62.5" fill="white"/> | ||||||
|  | <rect x="30" y="20" width="125" height="125" rx="62.5" fill="white"/> | ||||||
|  | <path fill-rule="evenodd" clip-rule="evenodd" d="M55.8789 105.714L69.3974 95.3593C76.5762 104.732 84.1998 109.051 92.6948 109.051C101.143 109.051 108.557 104.781 115.414 95.4832L129.119 105.59C119.232 118.996 106.932 126.079 92.6948 126.079C78.5049 126.079 66.0907 119.046 55.8789 105.714Z" fill="url(#paint0_linear)"/> | ||||||
|  | <path fill-rule="evenodd" clip-rule="evenodd" d="M92.6491 60.7078L68.5883 81.4406L57.4727 68.5407L92.6969 38.1885L127.647 68.5644L116.477 81.417L92.6491 60.7078Z" fill="url(#paint1_linear)"/> | ||||||
|  | </g> | ||||||
|  | <defs> | ||||||
|  | <filter id="filter0_d" x="0" y="0" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> | ||||||
|  | <feFlood flood-opacity="0" result="BackgroundImageFix"/> | ||||||
|  | <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/> | ||||||
|  | <feOffset dy="10"/> | ||||||
|  | <feGaussianBlur stdDeviation="15"/> | ||||||
|  | <feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.117647 0 0 0 0 0.211765 0 0 0 0.1 0"/> | ||||||
|  | <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/> | ||||||
|  | <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/> | ||||||
|  | </filter> | ||||||
|  | <linearGradient id="paint0_linear" x1="55.8789" y1="116.251" x2="129.119" y2="116.251" gradientUnits="userSpaceOnUse"> | ||||||
|  | <stop stop-color="#8930FD"/> | ||||||
|  | <stop offset="1" stop-color="#49CCF9"/> | ||||||
|  | </linearGradient> | ||||||
|  | <linearGradient id="paint1_linear" x1="57.4727" y1="67.6025" x2="127.647" y2="67.6025" gradientUnits="userSpaceOnUse"> | ||||||
|  | <stop stop-color="#FF02F0"/> | ||||||
|  | <stop offset="1" stop-color="#FFC800"/> | ||||||
|  | </linearGradient> | ||||||
|  | </defs> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										21
									
								
								packages/backend/src/apps/clickup/auth/generate-auth-url.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | import { URLSearchParams } from 'url'; | ||||||
|  |  | ||||||
|  | export default async function generateAuthUrl($) { | ||||||
|  |   const oauthRedirectUrlField = $.app.auth.fields.find( | ||||||
|  |     (field) => field.key == 'oAuthRedirectUrl' | ||||||
|  |   ); | ||||||
|  |   const redirectUri = oauthRedirectUrlField.value; | ||||||
|  |   const state = Math.random().toString(); | ||||||
|  |   const searchParams = new URLSearchParams({ | ||||||
|  |     client_id: $.auth.data.clientId, | ||||||
|  |     redirect_uri: redirectUri, | ||||||
|  |     state, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const url = `https://app.clickup.com/api?${searchParams.toString()}`; | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     url, | ||||||
|  |     originalState: state, | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								packages/backend/src/apps/clickup/auth/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | |||||||
|  | import generateAuthUrl from './generate-auth-url.js'; | ||||||
|  | import verifyCredentials from './verify-credentials.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/clickup/connections/add', | ||||||
|  |       placeholder: null, | ||||||
|  |       description: | ||||||
|  |         'When asked to input a redirect URL in ClickUp, 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, | ||||||
|  | }; | ||||||
| @@ -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; | ||||||
							
								
								
									
										31
									
								
								packages/backend/src/apps/clickup/auth/verify-credentials.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | 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.`); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const { data } = await $.http.post('/v2/oauth/token', { | ||||||
|  |     client_id: $.auth.data.clientId, | ||||||
|  |     client_secret: $.auth.data.clientSecret, | ||||||
|  |     code: $.auth.data.code, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     accessToken: data.access_token, | ||||||
|  |     tokenType: data.token_type, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const currentUser = await getCurrentUser($); | ||||||
|  |   const screenName = [currentUser.username, currentUser.email] | ||||||
|  |     .filter(Boolean) | ||||||
|  |     .join(' @ '); | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     clientId: $.auth.data.clientId, | ||||||
|  |     clientSecret: $.auth.data.clientSecret, | ||||||
|  |     screenName, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default verifyCredentials; | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | const addAuthHeader = ($, requestConfig) => { | ||||||
|  |   if ($.auth.data?.accessToken) { | ||||||
|  |     requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return requestConfig; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default addAuthHeader; | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | const getCurrentUser = async ($) => { | ||||||
|  |   const { data } = await $.http.get('/v2/user'); | ||||||
|  |   return data.user; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default getCurrentUser; | ||||||
							
								
								
									
										19
									
								
								packages/backend/src/apps/clickup/dynamic-data/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | |||||||
|  | import listAssignees from './list-assignees/index.js'; | ||||||
|  | import listFolders from './list-folders/index.js'; | ||||||
|  | import listLists from './list-lists/index.js'; | ||||||
|  | import listSpaces from './list-spaces/index.js'; | ||||||
|  | import listStatuses from './list-statuses/index.js'; | ||||||
|  | import listTags from './list-tags/index.js'; | ||||||
|  | import listTasks from './list-tasks/index.js'; | ||||||
|  | import listWorkspaces from './list-workspaces/index.js'; | ||||||
|  |  | ||||||
|  | export default [ | ||||||
|  |   listAssignees, | ||||||
|  |   listFolders, | ||||||
|  |   listLists, | ||||||
|  |   listSpaces, | ||||||
|  |   listStatuses, | ||||||
|  |   listTags, | ||||||
|  |   listTasks, | ||||||
|  |   listWorkspaces, | ||||||
|  | ]; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List assignees', | ||||||
|  |   key: 'listAssignees', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const assignees = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const listId = $.step.parameters.listId; | ||||||
|  |  | ||||||
|  |     if (!listId) { | ||||||
|  |       return assignees; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/list/${listId}/member`); | ||||||
|  |  | ||||||
|  |     if (data.members) { | ||||||
|  |       for (const member of data.members) { | ||||||
|  |         assignees.data.push({ | ||||||
|  |           value: member.id, | ||||||
|  |           name: member.username, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return assignees; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List folders', | ||||||
|  |   key: 'listFolders', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const folders = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const spaceId = $.step.parameters.spaceId; | ||||||
|  |  | ||||||
|  |     if (!spaceId) { | ||||||
|  |       return folders; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/space/${spaceId}/folder`); | ||||||
|  |  | ||||||
|  |     if (data.folders) { | ||||||
|  |       for (const folder of data.folders) { | ||||||
|  |         folders.data.push({ | ||||||
|  |           value: folder.id, | ||||||
|  |           name: folder.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return folders; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List lists', | ||||||
|  |   key: 'listLists', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const lists = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const folderId = $.step.parameters.folderId; | ||||||
|  |  | ||||||
|  |     if (!folderId) { | ||||||
|  |       return lists; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/folder/${folderId}/list`); | ||||||
|  |  | ||||||
|  |     if (data.lists) { | ||||||
|  |       for (const list of data.lists) { | ||||||
|  |         lists.data.push({ | ||||||
|  |           value: list.id, | ||||||
|  |           name: list.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return lists; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List spaces', | ||||||
|  |   key: 'listSpaces', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const spaces = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const workspaceId = $.step.parameters.workspaceId; | ||||||
|  |  | ||||||
|  |     if (!workspaceId) { | ||||||
|  |       return spaces; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/team/${workspaceId}/space`); | ||||||
|  |  | ||||||
|  |     if (data.spaces) { | ||||||
|  |       for (const space of data.spaces) { | ||||||
|  |         spaces.data.push({ | ||||||
|  |           value: space.id, | ||||||
|  |           name: space.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return spaces; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List statuses', | ||||||
|  |   key: 'listStatuses', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const statuses = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const listId = $.step.parameters.listId; | ||||||
|  |  | ||||||
|  |     if (!listId) { | ||||||
|  |       return statuses; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`/v2/list/${listId}`); | ||||||
|  |  | ||||||
|  |     if (data.statuses) { | ||||||
|  |       for (const status of data.statuses) { | ||||||
|  |         statuses.data.push({ | ||||||
|  |           value: status.status, | ||||||
|  |           name: status.status, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return statuses; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List tags', | ||||||
|  |   key: 'listTags', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const tags = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const spaceId = $.step.parameters.spaceId; | ||||||
|  |  | ||||||
|  |     if (!spaceId) { | ||||||
|  |       return spaceId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`v2/space/${spaceId}/tag`); | ||||||
|  |  | ||||||
|  |     if (data.tags) { | ||||||
|  |       for (const tag of data.tags) { | ||||||
|  |         tags.data.push({ | ||||||
|  |           value: tag.name, | ||||||
|  |           name: tag.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return tags; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List tasks', | ||||||
|  |   key: 'listTasks', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const tasks = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const listId = $.step.parameters.listId; | ||||||
|  |     let next = false; | ||||||
|  |  | ||||||
|  |     if (!listId) { | ||||||
|  |       return tasks; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const params = { | ||||||
|  |       order_by: 'created', | ||||||
|  |       reverse: true, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     do { | ||||||
|  |       const { data } = await $.http.get(`/v2/list/${listId}/task`, { params }); | ||||||
|  |       if (data.last_page) { | ||||||
|  |         next = false; | ||||||
|  |       } else { | ||||||
|  |         next = true; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (data.tasks) { | ||||||
|  |         for (const task of data.tasks) { | ||||||
|  |           tasks.data.push({ | ||||||
|  |             value: task.id, | ||||||
|  |             name: task.name, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } while (next); | ||||||
|  |  | ||||||
|  |     return tasks; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List workspaces', | ||||||
|  |   key: 'listWorkspaces', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const workspaces = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get('/v2/team'); | ||||||
|  |  | ||||||
|  |     if (data.teams) { | ||||||
|  |       for (const workspace of data.teams) { | ||||||
|  |         workspaces.data.push({ | ||||||
|  |           value: workspace.id, | ||||||
|  |           name: workspace.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return workspaces; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | import useCustomId from './use-custom-id/index.js'; | ||||||
|  |  | ||||||
|  | export default [useCustomId]; | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List workspaces when using custom id', | ||||||
|  |   key: 'listFieldsWhenUsingCustomId', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     if ($.step.parameters.useCustomId) { | ||||||
|  |       return [ | ||||||
|  |         { | ||||||
|  |           label: 'Workspace', | ||||||
|  |           key: 'workspaceId', | ||||||
|  |           type: 'dropdown', | ||||||
|  |           required: true, | ||||||
|  |           description: '', | ||||||
|  |           variables: true, | ||||||
|  |           source: { | ||||||
|  |             type: 'query', | ||||||
|  |             name: 'getDynamicData', | ||||||
|  |             arguments: [ | ||||||
|  |               { | ||||||
|  |                 name: 'key', | ||||||
|  |                 value: 'listWorkspaces', | ||||||
|  |               }, | ||||||
|  |             ], | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       ]; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										24
									
								
								packages/backend/src/apps/clickup/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,24 @@ | |||||||
|  | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import addAuthHeader from './common/add-auth-header.js'; | ||||||
|  | import auth from './auth/index.js'; | ||||||
|  | import triggers from './triggers/index.js'; | ||||||
|  | import dynamicData from './dynamic-data/index.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  | import dynamicFields from './dynamic-fields/index.js'; | ||||||
|  |  | ||||||
|  | export default defineApp({ | ||||||
|  |   name: 'ClickUp', | ||||||
|  |   key: 'clickup', | ||||||
|  |   baseUrl: 'https://clickup.com', | ||||||
|  |   apiBaseUrl: 'https://api.clickup.com/api', | ||||||
|  |   iconUrl: '{BASE_URL}/apps/clickup/assets/favicon.svg', | ||||||
|  |   authDocUrl: 'https://automatisch.io/docs/apps/clickup/connection', | ||||||
|  |   primaryColor: 'FD71AF', | ||||||
|  |   supportsConnections: true, | ||||||
|  |   beforeRequest: [addAuthHeader], | ||||||
|  |   auth, | ||||||
|  |   triggers, | ||||||
|  |   dynamicData, | ||||||
|  |   actions, | ||||||
|  |   dynamicFields, | ||||||
|  | }); | ||||||
							
								
								
									
										6
									
								
								packages/backend/src/apps/clickup/triggers/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | |||||||
|  | import newFolders from './new-folders/index.js'; | ||||||
|  | import newLists from './new-lists/index.js'; | ||||||
|  | import newTasks from './new-tasks/index.js'; | ||||||
|  | import updatedTask from './updated-task/index.js'; | ||||||
|  |  | ||||||
|  | export default [newFolders, newLists, newTasks, updatedTask]; | ||||||
							
								
								
									
										105
									
								
								packages/backend/src/apps/clickup/triggers/new-folders/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,105 @@ | |||||||
|  | import Crypto from 'crypto'; | ||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'New folders', | ||||||
|  |   key: 'newFolder', | ||||||
|  |   type: 'webhook', | ||||||
|  |   description: 'Triggers when a new folder is created.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: $.request.body, | ||||||
|  |       meta: { | ||||||
|  |         internalId: $.request.body.folder_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async testRun($) { | ||||||
|  |     const sampleEventData = { | ||||||
|  |       event: 'folderCreated', | ||||||
|  |       folder_id: '90180382912', | ||||||
|  |       webhook_id: Crypto.randomUUID(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: sampleEventData, | ||||||
|  |       meta: { | ||||||
|  |         internalId: '', | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async registerHook($) { | ||||||
|  |     const { workspaceId, spaceId } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const payload = { | ||||||
|  |       name: $.flow.id, | ||||||
|  |       endpoint: $.webhookUrl, | ||||||
|  |       events: ['folderCreated'], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if (spaceId) { | ||||||
|  |       payload.space_id = spaceId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v2/team/${workspaceId}/webhook`, | ||||||
|  |       payload | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     await $.flow.setRemoteWebhookId(data.id); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async unregisterHook($) { | ||||||
|  |     await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										129
									
								
								packages/backend/src/apps/clickup/triggers/new-lists/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,129 @@ | |||||||
|  | import Crypto from 'crypto'; | ||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'New lists', | ||||||
|  |   key: 'newLists', | ||||||
|  |   type: 'webhook', | ||||||
|  |   description: 'Triggers when a new list is created.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder', | ||||||
|  |       key: 'folderId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.spaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFolders', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.spaceId', | ||||||
|  |             value: '{parameters.spaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: $.request.body, | ||||||
|  |       meta: { | ||||||
|  |         internalId: $.request.body.list_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async testRun($) { | ||||||
|  |     const sampleEventData = { | ||||||
|  |       event: 'listCreated', | ||||||
|  |       list_id: '901800588812', | ||||||
|  |       webhook_id: Crypto.randomUUID(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: sampleEventData, | ||||||
|  |       meta: { | ||||||
|  |         internalId: sampleEventData.webhook_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async registerHook($) { | ||||||
|  |     const { workspaceId, spaceId, folderId } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const payload = { | ||||||
|  |       name: $.flow.id, | ||||||
|  |       endpoint: $.webhookUrl, | ||||||
|  |       events: ['listCreated'], | ||||||
|  |       space_id: spaceId, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if (folderId) { | ||||||
|  |       payload.folder_id = folderId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v2/team/${workspaceId}/webhook`, | ||||||
|  |       payload | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     await $.flow.setRemoteWebhookId(data.id); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async unregisterHook($) { | ||||||
|  |     await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										186
									
								
								packages/backend/src/apps/clickup/triggers/new-tasks/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,186 @@ | |||||||
|  | import Crypto from 'crypto'; | ||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'New tasks', | ||||||
|  |   key: 'newTasks', | ||||||
|  |   type: 'webhook', | ||||||
|  |   description: 'Triggers when a new task is created.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder', | ||||||
|  |       key: 'folderId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.spaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFolders', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.spaceId', | ||||||
|  |             value: '{parameters.spaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'List', | ||||||
|  |       key: 'listId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.folderId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listLists', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.folderId', | ||||||
|  |             value: '{parameters.folderId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Task', | ||||||
|  |       key: 'taskId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.listId'], | ||||||
|  |       description: | ||||||
|  |         'Choose an optional task to determine when this flow should be activated. In this scenario, only subtasks will initiate this flow.', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listTasks', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.listId', | ||||||
|  |             value: '{parameters.listId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: $.request.body, | ||||||
|  |       meta: { | ||||||
|  |         internalId: $.request.body.task_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async testRun($) { | ||||||
|  |     const sampleEventData = { | ||||||
|  |       event: 'taskCreated', | ||||||
|  |       task_id: '86enn7pg7', | ||||||
|  |       webhook_id: Crypto.randomUUID(), | ||||||
|  |       history_items: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: sampleEventData, | ||||||
|  |       meta: { | ||||||
|  |         internalId: sampleEventData.webhook_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async registerHook($) { | ||||||
|  |     const { workspaceId, spaceId, folderId, listId, taskId } = | ||||||
|  |       $.step.parameters; | ||||||
|  |  | ||||||
|  |     const payload = { | ||||||
|  |       name: $.flow.id, | ||||||
|  |       endpoint: $.webhookUrl, | ||||||
|  |       events: ['taskCreated'], | ||||||
|  |       space_id: spaceId, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if (folderId) { | ||||||
|  |       payload.folder_id = folderId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (listId) { | ||||||
|  |       payload.list_id = listId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (taskId) { | ||||||
|  |       payload.task_id = taskId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v2/team/${workspaceId}/webhook`, | ||||||
|  |       payload | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     await $.flow.setRemoteWebhookId(data.id); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async unregisterHook($) { | ||||||
|  |     await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										172
									
								
								packages/backend/src/apps/clickup/triggers/updated-task/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,172 @@ | |||||||
|  | import Crypto from 'crypto'; | ||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'Updated task', | ||||||
|  |   key: 'updatedTask', | ||||||
|  |   type: 'webhook', | ||||||
|  |   description: 'Triggers when a task is updated.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Workspace', | ||||||
|  |       key: 'workspaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listWorkspaces', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Space', | ||||||
|  |       key: 'spaceId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.workspaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listSpaces', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.workspaceId', | ||||||
|  |             value: '{parameters.workspaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Folder', | ||||||
|  |       key: 'folderId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.spaceId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFolders', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.spaceId', | ||||||
|  |             value: '{parameters.spaceId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'List', | ||||||
|  |       key: 'listId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       dependsOn: ['parameters.folderId'], | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listLists', | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: 'parameters.folderId', | ||||||
|  |             value: '{parameters.folderId}', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'What Changed?', | ||||||
|  |       key: 'whatChanged', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Status', value: 'taskStatusUpdated' }, | ||||||
|  |         { label: 'Assignee Added', value: 'taskAssigneeUpdated' }, | ||||||
|  |         { label: 'Priority', value: 'taskPriorityUpdated' }, | ||||||
|  |         { label: 'Tag Added', value: 'taskTagUpdated' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: $.request.body, | ||||||
|  |       meta: { | ||||||
|  |         internalId: Crypto.randomUUID(), | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async testRun($) { | ||||||
|  |     const sampleEventData = { | ||||||
|  |       event: 'taskUpdated', | ||||||
|  |       task_id: '86enn7pg7', | ||||||
|  |       webhook_id: Crypto.randomUUID(), | ||||||
|  |       history_items: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const dataItem = { | ||||||
|  |       raw: sampleEventData, | ||||||
|  |       meta: { | ||||||
|  |         internalId: sampleEventData.webhook_id, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem(dataItem); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async registerHook($) { | ||||||
|  |     const { workspaceId, spaceId, folderId, listId, whatChanged } = | ||||||
|  |       $.step.parameters; | ||||||
|  |  | ||||||
|  |     const payload = { | ||||||
|  |       name: $.flow.id, | ||||||
|  |       endpoint: $.webhookUrl, | ||||||
|  |       space_id: spaceId, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     payload.events = [whatChanged || 'taskUpdated']; | ||||||
|  |  | ||||||
|  |     if (folderId) { | ||||||
|  |       payload.folder_id = folderId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (listId) { | ||||||
|  |       payload.list_id = listId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v2/team/${workspaceId}/webhook`, | ||||||
|  |       payload | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     await $.flow.setRemoteWebhookId(data.id); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   async unregisterHook($) { | ||||||
|  |     await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										3
									
								
								packages/backend/src/apps/code/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | import runJavascript from './run-javascript/index.js'; | ||||||
|  |  | ||||||
|  | export default [runJavascript]; | ||||||
| @@ -0,0 +1,83 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Run Javascript', | ||||||
|  |   key: 'runJavascript', | ||||||
|  |   description: | ||||||
|  |     'Run browser Javascript code. You can not use NodeJS specific features and npm packages.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Inputs', | ||||||
|  |       key: 'inputs', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         'To be able to use data from previous steps, you need to expose them as input entries. You can access these input values in your code by using the `inputs` argument.', | ||||||
|  |       value: [ | ||||||
|  |         { | ||||||
|  |           key: '', | ||||||
|  |           value: '', | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Key', | ||||||
|  |           key: 'key', | ||||||
|  |           type: 'string', | ||||||
|  |           required: true, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'Value', | ||||||
|  |           key: 'value', | ||||||
|  |           type: 'string', | ||||||
|  |           required: true, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Code Snippet', | ||||||
|  |       key: 'codeSnippet', | ||||||
|  |       type: 'code', | ||||||
|  |       required: true, | ||||||
|  |       variables: false, | ||||||
|  |       value: | ||||||
|  |         'const code = async (inputs) => { \n  // E.g. if you have an input called username,\n  // you can access its value by calling inputs.username\n  // Return value will be used as output of this step.\n\n  return true;\n};', | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { inputs = [], codeSnippet } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const objectifiedInput = {}; | ||||||
|  |     for (const input of inputs) { | ||||||
|  |       if (input.key) { | ||||||
|  |         objectifiedInput[input.key] = input.value; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const ivm = (await import('isolated-vm')).default; | ||||||
|  |     const isolate = new ivm.Isolate({ memoryLimit: 128 }); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       const context = await isolate.createContext(); | ||||||
|  |       await context.global.set( | ||||||
|  |         'inputs', | ||||||
|  |         new ivm.ExternalCopy(objectifiedInput).copyInto() | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       const compiledCodeSnippet = await isolate.compileScript( | ||||||
|  |         `${codeSnippet}; code(inputs);` | ||||||
|  |       ); | ||||||
|  |       const codeFunction = await compiledCodeSnippet.run(context, { | ||||||
|  |         reference: true, | ||||||
|  |         promise: true, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       $.setActionItem({ raw: { output: await codeFunction.copy() } }); | ||||||
|  |     } finally { | ||||||
|  |       isolate.dispose(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										5
									
								
								packages/backend/src/apps/code/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 512 512"> | ||||||
|  |   <polyline points="160 368 32 256 160 144" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/> | ||||||
|  |   <polyline points="352 368 480 256 352 144" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/> | ||||||
|  |   <line x1="304" y1="96" x2="208" y2="416" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 519 B | 
							
								
								
									
										14
									
								
								packages/backend/src/apps/code/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  |  | ||||||
|  | export default defineApp({ | ||||||
|  |   name: 'Code', | ||||||
|  |   key: 'code', | ||||||
|  |   baseUrl: '', | ||||||
|  |   apiBaseUrl: '', | ||||||
|  |   iconUrl: '{BASE_URL}/apps/code/assets/favicon.svg', | ||||||
|  |   authDocUrl: '{DOCS_URL}/apps/code/connection', | ||||||
|  |   primaryColor: '000000', | ||||||
|  |   supportsConnections: false, | ||||||
|  |   actions, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,64 @@ | |||||||
|  | import { createHmac } from 'node:crypto'; | ||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create HMAC', | ||||||
|  |   key: 'createHmac', | ||||||
|  |   description: 'Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Algorithm', | ||||||
|  |       key: 'algorithm', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       value: 'sha256', | ||||||
|  |       description: 'Specifies the cryptographic hash function to use for HMAC generation.', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'SHA-256', value: 'sha256' }, | ||||||
|  |       ], | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Message', | ||||||
|  |       key: 'message', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The input message to be hashed. This is the value that will be processed to generate the HMAC.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Secret Key', | ||||||
|  |       key: 'secretKey', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The secret key used to create the HMAC.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Output Encoding', | ||||||
|  |       key: 'outputEncoding', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       value: 'hex', | ||||||
|  |       description: 'Specifies the encoding format for the HMAC digest output.', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'base64', value: 'base64' }, | ||||||
|  |         { label: 'base64url', value: 'base64url' }, | ||||||
|  |         { label: 'hex', value: 'hex' }, | ||||||
|  |       ], | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey) | ||||||
|  |       .update($.step.parameters.message) | ||||||
|  |       .digest($.step.parameters.outputEncoding); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: { | ||||||
|  |         hash | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,65 @@ | |||||||
|  | import crypto from 'node:crypto'; | ||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create Signature', | ||||||
|  |   key: 'createSignature', | ||||||
|  |   description: 'Create a digital signature using the specified algorithm, secret key, and message.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Algorithm', | ||||||
|  |       key: 'algorithm', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       value: 'RSA-SHA256', | ||||||
|  |       description: 'Specifies the cryptographic hash function to use for HMAC generation.', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'RSA-SHA256', value: 'RSA-SHA256' }, | ||||||
|  |       ], | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Message', | ||||||
|  |       key: 'message', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The input message to be signed.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Private Key', | ||||||
|  |       key: 'privateKey', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The RSA private key in PEM format used for signing.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Output Encoding', | ||||||
|  |       key: 'outputEncoding', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       value: 'hex', | ||||||
|  |       description: 'Specifies the encoding format for the digital signature output. This determines how the generated signature will be represented as a string.', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'base64', value: 'base64' }, | ||||||
|  |         { label: 'base64url', value: 'base64url' }, | ||||||
|  |         { label: 'hex', value: 'hex' }, | ||||||
|  |       ], | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const signer = crypto.createSign($.step.parameters.algorithm); | ||||||
|  |     signer.update($.step.parameters.message); | ||||||
|  |     signer.end(); | ||||||
|  |     const signature = signer.sign($.step.parameters.privateKey, $.step.parameters.outputEncoding); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: { | ||||||
|  |         signature | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										4
									
								
								packages/backend/src/apps/cryptography/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | import createHmac from './create-hmac/index.js'; | ||||||
|  | import createRsaSha256Signature from './create-rsa-sha256-signature/index.js'; | ||||||
|  |  | ||||||
|  | export default [createHmac, createRsaSha256Signature]; | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" width="100pt" height="100pt" version="1.1" viewBox="0 0 100 100"> | ||||||
|  |  <path d="m66.012 33h-3.0117v-11c0-7.1719-5.8281-13-13-13s-13 5.8281-13 13v11h-3.0117c-2.75 0-4.9883 2.2383-4.9883 4.9883v28.012c0 2.75 2.2383 4.9883 4.9883 4.9883h32.012c2.75 0 4.9883-2.2383 4.9883-4.9883v-28.012c0.011719-2.75-2.2266-4.9883-4.9766-4.9883zm-27.012-11c0-6.0703 4.9297-11 11-11s11 4.9297 11 11v11h-22zm30 44.012c0 1.6484-1.3398 2.9883-2.9883 2.9883h-32.023c-1.6484 0-2.9883-1.3398-2.9883-2.9883v-28.023c0-1.6484 1.3398-2.9883 2.9883-2.9883h32.023c1.6484 0 2.9883 1.3398 2.9883 2.9883zm-18 9.9883v14c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-14c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm20 8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm-32-8v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm-14-26c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm0-12c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm0 24c0 0.55078-0.44922 1-1 1h-7v7c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm66-12c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm-16-12c0-0.55078 0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1zm10 24v8c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-7h-7c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm-35-17c-2.7617 0-5 2.2383-5 5 0 2.4102 1.7188 4.4297 4 4.8984v5.1016c0 0.55078 0.44922 1 1 1s1-0.44922 1-1v-5.1016c2.2812-0.46094 4-2.4805 4-4.8984 0-2.7617-2.2383-5-5-5zm0 8c-1.6484 0-3-1.3516-3-3s1.3516-3 3-3 3 1.3516 3 3-1.3516 3-3 3z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										14
									
								
								packages/backend/src/apps/cryptography/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  |  | ||||||
|  | export default defineApp({ | ||||||
|  |   name: 'Cryptography', | ||||||
|  |   key: 'cryptography', | ||||||
|  |   iconUrl: '{BASE_URL}/apps/cryptography/assets/favicon.svg', | ||||||
|  |   authDocUrl: '{DOCS_URL}/apps/cryptography/connection', | ||||||
|  |   supportsConnections: false, | ||||||
|  |   baseUrl: '', | ||||||
|  |   apiBaseUrl: '', | ||||||
|  |   primaryColor: '001F52', | ||||||
|  |   actions, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Get value', | ||||||
|  |   key: 'getValue', | ||||||
|  |   description: 'Get value from the persistent datastore.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Key', | ||||||
|  |       key: 'key', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The key of your value to get.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const keyValuePair = await $.datastore.get({ | ||||||
|  |       key: $.step.parameters.key, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: keyValuePair, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										4
									
								
								packages/backend/src/apps/datastore/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | import getValue from './get-value/index.js'; | ||||||
|  | import setValue from './set-value/index.js'; | ||||||
|  |  | ||||||
|  | export default [getValue, setValue]; | ||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Set value', | ||||||
|  |   key: 'setValue', | ||||||
|  |   description: 'Set value to the persistent datastore.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Key', | ||||||
|  |       key: 'key', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The key of your value to set.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Value', | ||||||
|  |       key: 'value', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The value to set.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const keyValuePair = await $.datastore.set({ | ||||||
|  |       key: $.step.parameters.key, | ||||||
|  |       value: $.step.parameters.value, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: keyValuePair, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										13
									
								
								packages/backend/src/apps/datastore/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | <?xml version="1.0"?> | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon"> | ||||||
|  |   <defs> | ||||||
|  |     <style>.cls-1{fill:none;}</style> | ||||||
|  |   </defs> | ||||||
|  |   <title>datastore</title> | ||||||
|  |   <circle cx="23" cy="23" r="1"/> | ||||||
|  |   <rect x="8" y="22" width="12" height="2"/> | ||||||
|  |   <circle cx="23" cy="9" r="1"/> | ||||||
|  |   <rect x="8" y="8" width="12" height="2"/> | ||||||
|  |   <path d="M26,14a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H6A2,2,0,0,0,4,6v6a2,2,0,0,0,2,2H8v4H6a2,2,0,0,0-2,2v6a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V20a2,2,0,0,0-2-2H24V14ZM6,6H26v6H6ZM26,26H6V20H26Zm-4-8H10V14H22Z"/> | ||||||
|  |   <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 704 B | 
							
								
								
									
										14
									
								
								packages/backend/src/apps/datastore/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | |||||||
|  | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  |  | ||||||
|  | export default defineApp({ | ||||||
|  |   name: 'Datastore', | ||||||
|  |   key: 'datastore', | ||||||
|  |   iconUrl: '{BASE_URL}/apps/datastore/assets/favicon.svg', | ||||||
|  |   authDocUrl: '{DOCS_URL}/apps/datastore/connection', | ||||||
|  |   supportsConnections: false, | ||||||
|  |   baseUrl: '', | ||||||
|  |   apiBaseUrl: '', | ||||||
|  |   primaryColor: '001F52', | ||||||
|  |   actions, | ||||||
|  | }); | ||||||
| @@ -7,7 +7,7 @@ export default defineApp({ | |||||||
|   name: 'DeepL', |   name: 'DeepL', | ||||||
|   key: 'deepl', |   key: 'deepl', | ||||||
|   iconUrl: '{BASE_URL}/apps/deepl/assets/favicon.svg', |   iconUrl: '{BASE_URL}/apps/deepl/assets/favicon.svg', | ||||||
|   authDocUrl: 'https://automatisch.io/docs/apps/deepl/connection', |   authDocUrl: '{DOCS_URL}/apps/deepl/connection', | ||||||
|   supportsConnections: true, |   supportsConnections: true, | ||||||
|   baseUrl: 'https://deepl.com', |   baseUrl: 'https://deepl.com', | ||||||
|   apiBaseUrl: 'https://api.deepl.com', |   apiBaseUrl: 'https://api.deepl.com', | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ export default defineApp({ | |||||||
|   name: 'Delay', |   name: 'Delay', | ||||||
|   key: 'delay', |   key: 'delay', | ||||||
|   iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg', |   iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg', | ||||||
|   authDocUrl: 'https://automatisch.io/docs/apps/delay/connection', |   authDocUrl: '{DOCS_URL}/apps/delay/connection', | ||||||
|   supportsConnections: false, |   supportsConnections: false, | ||||||
|   baseUrl: '', |   baseUrl: '', | ||||||
|   apiBaseUrl: '', |   apiBaseUrl: '', | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ export default defineApp({ | |||||||
|   name: 'Discord', |   name: 'Discord', | ||||||
|   key: 'discord', |   key: 'discord', | ||||||
|   iconUrl: '{BASE_URL}/apps/discord/assets/favicon.svg', |   iconUrl: '{BASE_URL}/apps/discord/assets/favicon.svg', | ||||||
|   authDocUrl: 'https://automatisch.io/docs/apps/discord/connection', |   authDocUrl: '{DOCS_URL}/apps/discord/connection', | ||||||
|   supportsConnections: true, |   supportsConnections: true, | ||||||
|   baseUrl: 'https://discord.com', |   baseUrl: 'https://discord.com', | ||||||
|   apiBaseUrl: 'https://discord.com/api', |   apiBaseUrl: 'https://discord.com/api', | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								packages/backend/src/apps/disqus/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --> | ||||||
|  | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | ||||||
|  | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" | ||||||
|  | 	 width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve"> | ||||||
|  | <g id="background"> | ||||||
|  | 	<rect fill="#2E9FFF" width="200" height="200"/> | ||||||
|  | </g> | ||||||
|  | <g id="Layer_2"> | ||||||
|  | </g> | ||||||
|  | <path fill="#FFFFFF" d="M102.535,167.5c-16.518,0-31.621-6.036-43.298-16.021L30.5,155.405l11.102-27.401 | ||||||
|  | 	c-3.868-8.535-6.038-18.01-6.038-28.004c0-37.277,29.984-67.5,66.971-67.5c36.984,0,66.965,30.223,66.965,67.5 | ||||||
|  | 	C169.5,137.284,139.52,167.5,102.535,167.5z M139.102,99.807v-0.188c0-19.479-13.736-33.367-37.42-33.367h-25.58v67.5h25.201 | ||||||
|  | 	C125.171,133.753,139.102,119.284,139.102,99.807L139.102,99.807z M101.964,117.168h-7.482V82.841h7.482 | ||||||
|  | 	c10.989,0,18.283,6.265,18.283,17.07v0.188C120.247,110.995,112.953,117.168,101.964,117.168z"/> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										21
									
								
								packages/backend/src/apps/disqus/auth/generate-auth-url.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | 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 searchParams = new URLSearchParams({ | ||||||
|  |     client_id: $.auth.data.apiKey, | ||||||
|  |     scope: authScope.join(','), | ||||||
|  |     response_type: 'code', | ||||||
|  |     redirect_uri: redirectUri, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const url = `https://disqus.com/api/oauth/2.0/authorize/?${searchParams.toString()}`; | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     url, | ||||||
|  |   }); | ||||||
|  | } | ||||||